From b54fa84ac76082af57489698e969c8c9e4a51002 Mon Sep 17 00:00:00 2001 From: BOHA Date: Sat, 6 Jun 2026 23:56:00 +0200 Subject: [PATCH] =?UTF-8?q?feat(mui):=20migrate=20Leave=20Approval=20(Schv?= =?UTF-8?q?alov=C3=A1n=C3=AD)=20onto=20MUI=20kit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- src/admin/pages/LeaveApproval.tsx | 536 ++++++++++++++---------------- 1 file changed, 255 insertions(+), 281 deletions(-) diff --git a/src/admin/pages/LeaveApproval.tsx b/src/admin/pages/LeaveApproval.tsx index 748ee28..f5f7733 100644 --- a/src/admin/pages/LeaveApproval.tsx +++ b/src/admin/pages/LeaveApproval.tsx @@ -3,17 +3,33 @@ import { useQuery } from "@tanstack/react-query"; import { useAuth } from "../context/AuthContext"; import { useAlert } from "../context/AlertContext"; import { motion } from "framer-motion"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; import { formatDate, formatDatetime } from "../utils/attendanceHelpers"; import { czechPlural } from "../utils/formatters"; import { leavePendingOptions, leaveProcessedOptions, } from "../lib/queries/leave"; -import ConfirmModal from "../components/ConfirmModal"; import Forbidden from "../components/Forbidden"; -import FormModal from "../components/FormModal"; -import FormField from "../components/FormField"; import { useApiMutation } from "../lib/queries/mutations"; +import { + Button, + Card, + DataTable, + ConfirmDialog, + Modal, + StatusChip, + Field, + TextField, + PageHeader, + EmptyState, + LoadingState, + Tabs, + TabPanel, + type DataColumn, + type TabDef, +} from "../ui"; const API_BASE = "/api/admin"; @@ -23,12 +39,6 @@ const leaveTypeLabels: Record = { unpaid: "Neplacené volno", }; -const leaveTypeClasses: Record = { - vacation: "badge-vacation", - sick: "badge-sick", - unpaid: "badge-unpaid", -}; - const statusLabels: Record = { pending: "Čeká na schválení", approved: "Schváleno", @@ -36,13 +46,25 @@ const statusLabels: Record = { cancelled: "Zrušeno", }; -const statusClasses: Record = { - pending: "badge-pending", - approved: "badge-approved", - rejected: "badge-rejected", - cancelled: "badge-cancelled", +// Leave type → StatusChip color +const leaveTypeColor: Record< + string, + "info" | "error" | "default" | "success" | "warning" +> = { + vacation: "info", + sick: "error", + unpaid: "default", }; +// Status → StatusChip color +const statusColor: Record = + { + pending: "warning", + approved: "success", + rejected: "error", + cancelled: "default", + }; + interface RawLeaveRequest { id: number; leave_type: string; @@ -192,290 +214,243 @@ export default function LeaveApproval() { }; if (loading) { - return ( -
-
-
- ); + return ; } + const tabs: TabDef[] = [ + { value: "pending", label: `Ke schválení (${pendingCount})` }, + { value: "processed", label: "Vyřízené" }, + ]; + + const processedColumns: DataColumn[] = [ + { + key: "employee_name", + header: "Zaměstnanec", + width: "16%", + bold: true, + render: (req) => req.employee_name, + }, + { + key: "leave_type", + header: "Typ", + width: "13%", + render: (req) => ( + + ), + }, + { + key: "date_from", + header: "Od", + width: "10%", + mono: true, + render: (req) => formatDate(req.date_from), + }, + { + key: "date_to", + header: "Do", + width: "10%", + mono: true, + render: (req) => formatDate(req.date_to), + }, + { + key: "total_days", + header: "Dny", + width: "6%", + mono: true, + render: (req) => String(req.total_days), + }, + { + key: "status", + header: "Stav", + width: "13%", + render: (req) => ( + + ), + }, + { + key: "reviewer_name", + header: "Schválil", + width: "12%", + render: (req) => req.reviewer_name || "—", + }, + { + key: "reviewer_note", + header: "Poznámka", + width: "12%", + render: (req) => + req.reviewer_note ? ( + + {req.reviewer_note.length > 40 + ? `${req.reviewer_note.substring(0, 40)}...` + : req.reviewer_note} + + ) : ( + "—" + ), + }, + { + key: "reviewed_at", + header: "Vyřízeno", + width: "8%", + mono: true, + render: (req) => formatDatetime(req.reviewed_at), + }, + ]; + return ( -
+ -
-

Schvalování nepřítomnosti

-

- {pendingCount > 0 + 0 ? `${pendingCount} ${czechPlural(pendingCount, "žádost čeká", "žádosti čekají", "žádostí čeká")} na schválení` - : "Žádné čekající žádosti"} -

-
+ : "Žádné čekající žádosti" + } + />
- {/* Tabs */} -
- - -
+ setActiveTab(v as "pending" | "processed")} + tabs={tabs} + />
{/* Pending Tab */} - {activeTab === "pending" && ( - - {pendingRequests.length === 0 ? ( -
-
-
- - - - -

Žádné čekající žádosti

-
-
-
- ) : ( -
- {pendingRequests.map((req) => ( -
-
-
+ {pendingRequests.length === 0 ? ( + + + + ) : ( + + {pendingRequests.map((req) => ( + + + + -
-
- - {req.employee_name} - - - {leaveTypeLabels[req.leave_type] || req.leave_type} - -
-
- - {formatDate(req.date_from)} —{" "} - {formatDate(req.date_to)} - - - {req.total_days}{" "} - {czechPlural(req.total_days, "den", "dny", "dnů")} ( - {req.total_hours}h) - - - Podáno: {formatDatetime(req.created_at)} - -
- {req.notes && ( -
- {req.notes} -
- )} -
-
+ {req.employee_name} + + + + + + {formatDate(req.date_from)} —{" "} + {formatDate(req.date_to)} + + + {req.total_days}{" "} + {czechPlural(req.total_days, "den", "dny", "dnů")} ( + {req.total_hours}h) + + + Podáno: {formatDatetime(req.created_at)} + + + {req.notes && ( + - - -
-
-
-
- ))} -
- )} -
- )} + {req.notes} +
+ )} + + + + + + + + ))} + + )} + {/* Processed Tab */} - {activeTab === "processed" && ( - -
- {processedRequests.length === 0 ? ( -
-

Zatím žádné vyřízené žádosti

-
- ) : ( -
- - - - - - - - - - - - - - - - {processedRequests.map((req) => ( - - - - - - - - - - - - ))} - -
ZaměstnanecTypOdDoDnyStavSchválilPoznámkaVyřízeno
- {req.employee_name} - - - {leaveTypeLabels[req.leave_type] || req.leave_type} - - - {formatDate(req.date_from)} - - {formatDate(req.date_to)} - {req.total_days} - - {statusLabels[req.status] || req.status} - - {req.reviewer_name || "—"} - {req.reviewer_note ? ( - - {req.reviewer_note.length > 40 - ? `${req.reviewer_note.substring(0, 40)}...` - : req.reviewer_note} - - ) : ( - "—" - )} - - {formatDatetime(req.reviewed_at)} -
-
- )} -
-
- )} + + + + columns={processedColumns} + rows={processedRequests} + rowKey={(req) => req.id} + empty={} + /> + + {/* Approve Confirmation */} - setApproveModal({ open: false, request: null })} onConfirm={handleApprove} @@ -486,12 +461,11 @@ export default function LeaveApproval() { : "" } confirmText="Schválit" - type="info" loading={approveMutation.isPending} /> {/* Reject Modal */} - { setRejectModal({ open: false, request: null }); @@ -499,31 +473,31 @@ export default function LeaveApproval() { }} onSubmit={handleReject} title="Zamítnout žádost" - submitLabel="Zamítnout" + submitText="Zamítnout" loading={rejectMutation.isPending} > {rejectModal.request && ( <> -

+ {rejectModal.request.employee_name} —{" "} {leaveTypeLabels[rejectModal.request.leave_type]},{" "} {formatDate(rejectModal.request.date_from)} —{" "} {formatDate(rejectModal.request.date_to)} ( {rejectModal.request.total_days} dnů) -

- -