From 1074adee0dbea70582b7a789f459c66373daa75b Mon Sep 17 00:00:00 2001 From: BOHA Date: Sat, 6 Jun 2026 23:40:45 +0200 Subject: [PATCH] =?UTF-8?q?feat(mui):=20migrate=20Leave=20Requests=20(?= =?UTF-8?q?=C5=BD=C3=A1dosti)=20onto=20MUI=20kit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces admin-* CSS, ConfirmModal, and legacy markup with PageHeader, Card, DataTable, ConfirmDialog, StatusChip, EmptyState, LoadingState. Dual StatusChip badges per row: leave-type (info/error/default) and status (warning/success/error/default). Note cell preserved with Box title-tooltip and ellipsis truncation. cancelMutation and invalidate: ["leave-requests", "leave", "attendance"] preserved verbatim. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/admin/pages/LeaveRequests.tsx | 339 +++++++++++++++++------------- 1 file changed, 192 insertions(+), 147 deletions(-) diff --git a/src/admin/pages/LeaveRequests.tsx b/src/admin/pages/LeaveRequests.tsx index be12bd1..788973a 100644 --- a/src/admin/pages/LeaveRequests.tsx +++ b/src/admin/pages/LeaveRequests.tsx @@ -3,11 +3,22 @@ import { useQuery } from "@tanstack/react-query"; import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; import { motion } from "framer-motion"; +import Box from "@mui/material/Box"; import Forbidden from "../components/Forbidden"; import { formatDate, formatDatetime } from "../utils/attendanceHelpers"; -import ConfirmModal from "../components/ConfirmModal"; import { leaveRequestsOptions, type LeaveRequest } from "../lib/queries/leave"; import { useApiMutation } from "../lib/queries/mutations"; +import { + Button, + Card, + DataTable, + ConfirmDialog, + StatusChip, + PageHeader, + EmptyState, + LoadingState, + type DataColumn, +} from "../ui"; const API_BASE = "/api/admin"; @@ -24,18 +35,40 @@ 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", }; -const leaveTypeClasses: Record = { - vacation: "badge-vacation", - sick: "badge-sick", - unpaid: "badge-unpaid", -}; +// Status → StatusChip color +const statusColor: Record = + { + pending: "warning", + approved: "success", + rejected: "error", + cancelled: "default", + }; + +const CancelIcon = ( + + + + +); export default function LeaveRequests() { const alert = useAlert(); @@ -72,167 +105,179 @@ export default function LeaveRequests() { } }; - function renderNoteCell(req: LeaveRequest) { - const truncate = (text: string) => - text.length > 40 ? `${text.substring(0, 40)}...` : text; - if (req.status === "rejected" && req.reviewer_note) { - return ( - - {truncate(req.reviewer_note)} - - ); - } - if (req.notes) { - return ( - - {truncate(req.notes)} - - ); - } - return ; + if (isPending) { + return ; } - if (isPending) { - return ( -
-
-
- ); - } + const columns: DataColumn[] = [ + { + key: "leave_type", + header: "Typ", + width: "14%", + render: (req) => ( + + ), + }, + { + key: "date_from", + header: "Od", + width: "11%", + mono: true, + render: (req) => formatDate(req.date_from), + }, + { + key: "date_to", + header: "Do", + width: "11%", + mono: true, + render: (req) => formatDate(req.date_to), + }, + { + key: "total_days", + header: "Dny", + width: "7%", + mono: true, + render: (req) => String(req.total_days), + }, + { + key: "total_hours", + header: "Hodiny", + width: "8%", + mono: true, + render: (req) => `${req.total_hours}h`, + }, + { + key: "status", + header: "Stav", + width: "16%", + render: (req) => ( + + ), + }, + { + key: "notes", + header: "Poznámka", + width: "20%", + render: (req) => { + const truncate = (text: string) => + text.length > 40 ? `${text.substring(0, 40)}...` : text; + if (req.status === "rejected" && req.reviewer_note) { + return ( + + {truncate(req.reviewer_note)} + + ); + } + if (req.notes) { + return ( + + {truncate(req.notes)} + + ); + } + return ( + + — + + ); + }, + }, + { + key: "created_at", + header: "Podáno", + width: "13%", + mono: true, + render: (req) => formatDatetime(req.created_at), + }, + { + key: "actions", + header: "Akce", + width: "8%", + align: "right", + render: (req) => + req.status === "pending" ? ( + + ) : null, + }, + ]; return ( -
+ -
-

Moje žádosti

-

Přehled žádostí o nepřítomnost

-
+
-
- {requests.length === 0 ? ( -
-
- - - - - - -
-

Zatím nemáte žádné žádosti

-

- Novou žádost můžete podat na stránce Docházka -

-
- ) : ( -
- - - - - - - - - - - - - - - - {requests.map((req) => ( - - - - - - - - - - - - ))} - -
TypOdDoDnyHodinyStavPoznámkaPodánoAkce
- - {leaveTypeLabels[req.leave_type] || req.leave_type} - - - {formatDate(req.date_from)} - {formatDate(req.date_to)}{req.total_days}{req.total_hours}h - - {statusLabels[req.status] || req.status} - - - {renderNoteCell(req)} - - {formatDatetime(req.created_at)} - -
- {req.status === "pending" && ( - - )} -
-
-
- )} -
+ + + columns={columns} + rows={requests} + rowKey={(req) => req.id} + empty={ + + } + /> +
- setCancelModal({ open: false, id: null })} onConfirm={handleCancel} title="Zrušit žádost" message="Opravdu chcete zrušit tuto žádost o nepřítomnost?" confirmText="Zrušit žádost" - type="warning" loading={cancelMutation.isPending} /> -
+ ); }