import { useMemo, useRef, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { createLeaveRequest, fetchLeaveBalances, fetchLeaveRequests, fetchLeaveTypes, patchLeaveRequest, submitLeaveRequest, type LeaveRequestItem, type LeaveRequestStatus, type LeaveSession, } from "../api/leave"; import { useAuth } from "../auth/AuthContext"; import { hasPermission } from "../auth/permissions"; import { PageHeader } from "../components/common/PageHeader"; import { Button } from "../components/ui/button"; import { Tabs, TabsList, TabsTrigger } from "../components/ui/tabs"; import { PageScaffold } from "../components/patterns/PageScaffold"; import { resolveErrorMessage } from "../lib/resolveErrorMessage"; type LeaveStatusFilter = "all" | LeaveRequestStatus; const STATUS_TABS: Array<{ value: LeaveStatusFilter; label: string }> = [ { value: "all", label: "全部" }, { value: "draft", label: "草稿" }, { value: "reviewing", label: "审批中" }, { value: "approved", label: "已通过" }, ]; const STATUS_LABEL: Record = { draft: "草稿", reviewing: "审批中", approved: "已通过", rejected: "已驳回", cancelled: "已取消", }; function todayDate() { return new Date().toISOString().slice(0, 10); } function formatSession(value: LeaveSession) { return value === "am" ? "上午" : "下午"; } function formatLeaveRange(item: LeaveRequestItem) { return `${item.startDate} ${formatSession(item.startSession)} - ${item.endDate} ${formatSession(item.endSession)}`; } function LeaveRecordCard({ item, isSaving, onEdit, onSubmit, }: { item: LeaveRequestItem; isSaving: boolean; onEdit: (item: LeaveRequestItem) => void; onSubmit: (item: LeaveRequestItem) => void; }) { const canEdit = item.status === "draft" || item.status === "rejected"; const canSubmit = item.status === "draft" || item.status === "rejected"; return (
{item.leaveType?.name ?? "请假"}
{formatLeaveRange(item)}
{STATUS_LABEL[item.status]}
天数
{item.requestedDays}
附件
{item.attachment?.originalName ?? "-"}
{item.reason ?

{item.reason}

: null} {canEdit || canSubmit ? (
{canEdit ? ( ) : null} {canSubmit ? ( ) : null}
) : null}
); } export function MobileLeavePage() { const queryClient = useQueryClient(); const { permissions } = useAuth(); const canCreateLeave = hasPermission(permissions, "leave.create"); const [statusFilter, setStatusFilter] = useState("all"); const [editingItem, setEditingItem] = useState(null); const [leaveTypeId, setLeaveTypeId] = useState(""); const [startDate, setStartDate] = useState(todayDate); const [endDate, setEndDate] = useState(todayDate); const [startSession, setStartSession] = useState("am"); const [endSession, setEndSession] = useState("pm"); const [reason, setReason] = useState(""); const [attachmentFile, setAttachmentFile] = useState(null); const attachmentInputRef = useRef(null); const leaveTypesQuery = useQuery({ queryKey: ["mobile", "leave", "types"], queryFn: () => fetchLeaveTypes({ enabled: true }), }); const balancesQuery = useQuery({ queryKey: ["mobile", "leave", "balances"], queryFn: () => fetchLeaveBalances(), }); const requestsQuery = useQuery({ queryKey: ["mobile", "leave", "requests", statusFilter], queryFn: () => fetchLeaveRequests({ status: statusFilter === "all" ? undefined : statusFilter, scope: "own", page: 1, pageSize: 20, }), }); const invalidateLeaveQueries = async () => { await Promise.all([ queryClient.invalidateQueries({ queryKey: ["mobile", "leave"] }), queryClient.invalidateQueries({ queryKey: ["leave"] }), ]); }; const resetForm = () => { setEditingItem(null); setLeaveTypeId(""); setStartDate(todayDate()); setEndDate(todayDate()); setStartSession("am"); setEndSession("pm"); setReason(""); setAttachmentFile(null); if (attachmentInputRef.current) { attachmentInputRef.current.value = ""; } }; const createMutation = useMutation({ mutationFn: () => createLeaveRequest({ leaveTypeId, startDate, endDate, startSession, endSession, reason, file: attachmentFile, }), onSuccess: async () => { toast.success("请假草稿已创建"); resetForm(); await invalidateLeaveQueries(); }, onError: (error) => { toast.error(resolveErrorMessage(error)); }, }); const updateMutation = useMutation({ mutationFn: () => { if (!editingItem) { throw new Error("未选择请假单"); } return patchLeaveRequest(editingItem.id, { leaveTypeId, startDate, endDate, startSession, endSession, reason, file: attachmentFile, }); }, onSuccess: async () => { toast.success("请假单已保存"); resetForm(); await invalidateLeaveQueries(); }, onError: (error) => { toast.error(resolveErrorMessage(error)); }, }); const submitMutation = useMutation({ mutationFn: (item: LeaveRequestItem) => submitLeaveRequest(item.id), onSuccess: async () => { toast.success("请假单已提交"); await invalidateLeaveQueries(); }, onError: (error) => { toast.error(resolveErrorMessage(error)); }, }); const leaveTypes = leaveTypesQuery.data?.items ?? []; const records = requestsQuery.data?.items ?? []; const balances = balancesQuery.data?.items ?? []; const balanceSummary = useMemo( () => balances.reduce( (summary, item) => ({ available: summary.available + item.available, reserved: summary.reserved + item.reserved, used: summary.used + item.used, }), { available: 0, reserved: 0, used: 0 }, ), [balances], ); const isSaving = createMutation.isPending || updateMutation.isPending || submitMutation.isPending; const startEditing = (item: LeaveRequestItem) => { setEditingItem(item); setLeaveTypeId(item.leaveType?.id ?? ""); setStartDate(item.startDate); setEndDate(item.endDate); setStartSession(item.startSession); setEndSession(item.endSession); setReason(item.reason ?? ""); setAttachmentFile(null); window.scrollTo({ top: 0, behavior: "smooth" }); }; return (
可用
{balanceSummary.available.toFixed(1)}
冻结
{balanceSummary.reserved.toFixed(1)}
已用
{balanceSummary.used.toFixed(1)}

{editingItem ? "编辑请假" : "新建请假"}

{editingItem ? ( ) : null}