From 80803cc8684ec7356c258b2b8b89cc2be679c89c Mon Sep 17 00:00:00 2001 From: BOHA Date: Sat, 6 Jun 2026 14:53:28 +0200 Subject: [PATCH] refactor(modals): migrate 8 ad-hoc modals to the shared FormModal BulkAttendanceModal, ShiftFormModal, OrderConfirmationModal, DashQuickActions trip modal, the Attendance leave modal, and DashProfile's 3 modals (edit profile, 2FA setup, 2FA disable) now use the shared FormModal instead of hand-rolled overlays, inheriting the canonical open/close animation, blurred backdrop, ESC-to-close, click-outside, scroll-lock, staggered reveal, mobile full-screen and reduced-motion. Footers stay pinned via the real FormModal footer + submitDisabled; multi-step modals (2FA setup, order confirmation) drive title/footer/close-guards per step. Behaviour preserved. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/admin/components/BulkAttendanceModal.tsx | 293 +++---- .../components/OrderConfirmationModal.tsx | 516 +++++------ src/admin/components/ShiftFormModal.tsx | 463 +++++----- .../components/dashboard/DashProfile.tsx | 819 ++++++++---------- .../components/dashboard/DashQuickActions.tsx | 477 +++++----- src/admin/pages/Attendance.tsx | 270 +++--- 6 files changed, 1231 insertions(+), 1607 deletions(-) diff --git a/src/admin/components/BulkAttendanceModal.tsx b/src/admin/components/BulkAttendanceModal.tsx index ce98657..1cd0212 100644 --- a/src/admin/components/BulkAttendanceModal.tsx +++ b/src/admin/components/BulkAttendanceModal.tsx @@ -1,6 +1,5 @@ -import { motion, AnimatePresence } from "framer-motion"; +import FormModal from "./FormModal"; import AdminDatePicker from "./AdminDatePicker"; -import useModalLock from "../hooks/useModalLock"; interface BulkAttendanceForm { month: string; @@ -39,182 +38,128 @@ export default function BulkAttendanceModal({ toggleUser, toggleAllUsers, }: BulkAttendanceModalProps) { - useModalLock(isOpen); - return ( - - {isOpen && ( - -
!submitting && onClose()} + +

+ Vytvoří záznamy pro všechny pracovní dny. Svátky se automaticky označí. + Existující záznamy se přeskočí. +

+ +
+
+ + setForm({ ...form, month: val })} /> - + +
+ +
-
-

- Vyplnit docházku za měsíc -

-

- Vytvoří záznamy pro všechny pracovní dny. Svátky se automaticky - označí. Existující záznamy se přeskočí. -

-
+ {users.map((user) => ( + + ))} +
+ + Vybráno: {form.user_ids.length} z {users.length} + +
-
-
-
- - setForm({ ...form, month: val })} - /> -
+
+
+ + setForm({ ...form, arrival_time: val })} + /> +
+
+ + setForm({ ...form, departure_time: val })} + /> +
+
-
- -
- {users.map((user) => ( - - ))} -
- - Vybráno: {form.user_ids.length} z {users.length} - -
- -
-
- - - setForm({ ...form, arrival_time: val }) - } - /> -
-
- - - setForm({ ...form, departure_time: val }) - } - /> -
-
- -
-
- - - setForm({ ...form, break_start_time: val }) - } - /> -
-
- - - setForm({ ...form, break_end_time: val }) - } - /> -
-
-
-
- -
- - -
-
- - )} - +
+
+ + setForm({ ...form, break_start_time: val })} + /> +
+
+ + setForm({ ...form, break_end_time: val })} + /> +
+
+
+ ); } diff --git a/src/admin/components/OrderConfirmationModal.tsx b/src/admin/components/OrderConfirmationModal.tsx index 4577d1c..4dc8047 100644 --- a/src/admin/components/OrderConfirmationModal.tsx +++ b/src/admin/components/OrderConfirmationModal.tsx @@ -1,5 +1,5 @@ import { useState, useCallback } from "react"; -import { motion, AnimatePresence } from "framer-motion"; +import FormModal from "./FormModal"; import { useAlert } from "../context/AlertContext"; interface ConfirmationItem { @@ -103,294 +103,250 @@ export default function OrderConfirmationModal({ }, [defaultVatRate]); return ( - - {isOpen && ( - -
- -
-

+ {step === "choose" ? ( +
+
+ +
+

+ Čeština + +
+
-
- {step === "choose" ? ( -
-
- -
- - -
-
- -
- -
- - -
-
- -
- -

- Jak chcete připravit potvrzení objednávky? -

- - -
-
- ) : ( -
-
- - - - - - - - - - - - {items.map((item, i) => ( - - - - - - - - - ))} - -
PopisMn.Jedn.Cena%DPH -
- - updateItem(i, "description", e.target.value) - } - className="admin-form-input" - style={{ minWidth: "200px" }} - /> - - - updateItem( - i, - "quantity", - Number(e.target.value) || 0, - ) - } - className="admin-form-input" - style={{ width: "80px" }} - step="0.001" - /> - - - updateItem(i, "unit", e.target.value) - } - className="admin-form-input" - style={{ width: "60px" }} - /> - - - updateItem( - i, - "unit_price", - Number(e.target.value) || 0, - ) - } - className="admin-form-input" - style={{ width: "100px" }} - step="0.01" - /> - - - updateItem( - i, - "vat_rate", - Number(e.target.value) || 0, - ) - } - className="admin-form-input" - style={{ width: "70px" }} - step="1" - /> - - -
-
- -
- )} +
+ +
+ +
+
-
- {step === "edit" && ( +
+ +

+ Jak chcete připravit potvrzení objednávky? +

+ - +
+ Generuji... + ) : ( + "Použít položky z objednávky" )} - {step === "choose" && ( - + + +
+ +
+ +
+
+ ) : ( +
+
+ + + + + + + + + + + + {items.map((item, i) => ( + + + + + + + + + ))} + +
PopisMn.Jedn.Cena%DPH +
+ + updateItem(i, "description", e.target.value) + } + className="admin-form-input" + style={{ minWidth: "200px" }} + /> + + + updateItem(i, "quantity", Number(e.target.value) || 0) + } + className="admin-form-input" + style={{ width: "80px" }} + step="0.001" + /> + + updateItem(i, "unit", e.target.value)} + className="admin-form-input" + style={{ width: "60px" }} + /> + + + updateItem( + i, + "unit_price", + Number(e.target.value) || 0, + ) + } + className="admin-form-input" + style={{ width: "100px" }} + step="0.01" + /> + + + updateItem(i, "vat_rate", Number(e.target.value) || 0) + } + className="admin-form-input" + style={{ width: "70px" }} + step="1" + /> + + +
+
+ + +
+ + +
+
)} - + ); } diff --git a/src/admin/components/ShiftFormModal.tsx b/src/admin/components/ShiftFormModal.tsx index 390b544..6de76db 100644 --- a/src/admin/components/ShiftFormModal.tsx +++ b/src/admin/components/ShiftFormModal.tsx @@ -1,6 +1,5 @@ -import { motion, AnimatePresence } from "framer-motion"; import AdminDatePicker from "./AdminDatePicker"; -import useModalLock from "../hooks/useModalLock"; +import FormModal from "./FormModal"; import { calcFormWorkMinutes, calcProjectMinutesTotal, @@ -216,7 +215,6 @@ export default function ShiftFormModal({ onShiftDateChange, editingRecord, }: ShiftFormModalProps) { - useModalLock(isOpen); const isCreate = mode === "create"; const isWorkType = form.leave_type === "work"; @@ -247,276 +245,209 @@ export default function ShiftFormModal({ }; return ( - - {isOpen && ( - -
- -
-

- {isCreate ? "Přidat záznam docházky" : "Upravit docházku"} -

- {!isCreate && editingRecord && ( -

- {editingRecord.user_name} —{" "} - {formatDate(editingRecord.shift_date)} -

- )} + +
+ {isCreate ? ( +
+
+ +
+
+ + onShiftDateChange(val)} + /> +
+
+ ) : ( +
+ + updateField("shift_date", val)} + /> +
+ )} -
-
- {isCreate ? ( -
-
- - -
-
- - onShiftDateChange(val)} - /> -
-
- ) : ( -
- - updateField("shift_date", val)} - /> -
- )} +
+ + +
-
- - -
+ {!isWorkType && ( +
+ + + updateField("leave_hours", parseFloat(e.target.value)) + } + min="0.5" + max="24" + step="0.5" + className="admin-form-input" + /> + {isCreate && ( + 8 hodin = celý den + )} +
+ )} - {!isWorkType && ( -
- - - updateField("leave_hours", parseFloat(e.target.value)) - } - min="0.5" - max="24" - step="0.5" - className="admin-form-input" - /> - {isCreate && ( - - 8 hodin = celý den - - )} -
- )} - - {isWorkType && ( - <> -
-
- - updateField("arrival_date", val)} - /> -
-
- - updateField("arrival_time", val)} - /> -
-
- -
-
- - - updateField("break_start_date", val) - } - /> -
-
- - - updateField("break_start_time", val) - } - /> -
-
- -
-
- - updateField("break_end_date", val)} - /> -
-
- - updateField("break_end_time", val)} - /> -
-
- -
-
- - updateField("departure_date", val)} - /> -
-
- - updateField("departure_time", val)} - /> -
-
- - )} - - {isWorkType && projectList.length > 0 && ( -
- - - {projectLogs.map((log, i) => ( - - ))} - -
- )} - -
- -