refactor: fix all Low findings from FLAWS_REPORT audit
- Auth: TOTP params from config, JWT error logging, audit log failure logging, replaced_by_hash validation on token rotation - Invoices: remove dead VAT code, consistent PDF permissions, WebP magic-byte detection, deduped exchange-rate fetches - Orders/Offers: multipart limit from config, use paginated() helper, payment method from DB in PDF - Projects: verify project exists before creating note - Attendance: action_type enum validation, consistent local-time shift_date construction, holiday attendance in work fund, trips.view permission on last-km query - Users: paginated() helper usage, remove duplicate dashboard keys, parallel currency conversion, single hashToken implementation - Frontend: memoized customInput, reliable print onload, modal prop standardization (isOpen), ConfirmModal type icons, id===0 key fallback, Login useCallback, CompanySettings ConfirmModal, Attendance timeout cleanup, Dashboard memoization, beforeunload dirty-state warnings on Invoice/Offer/Order detail - Schema: invoice_alert_log timestamp, config/env comment on Date.prototype.toJSON override - Utils: exchange-rate inflight dedup Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -165,17 +165,22 @@ export default function AdminDatePicker({
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const commonProps = {
|
||||
selected: toDate(value),
|
||||
onChange: handleChange,
|
||||
locale: "cs",
|
||||
customInput: (
|
||||
const customInput = useMemo(
|
||||
() => (
|
||||
<CustomInput
|
||||
required={required}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
/>
|
||||
),
|
||||
[required, placeholder, disabled],
|
||||
);
|
||||
|
||||
const commonProps = {
|
||||
selected: toDate(value),
|
||||
onChange: handleChange,
|
||||
locale: "cs",
|
||||
customInput,
|
||||
minDate: parseMinMax(minDate),
|
||||
maxDate: parseMinMax(maxDate),
|
||||
popperPlacement: "bottom-start" as const,
|
||||
|
||||
@@ -87,7 +87,7 @@ function renderProjectCell(record: AttendanceRecord): React.ReactNode {
|
||||
}
|
||||
return (
|
||||
<span
|
||||
key={log.id || i}
|
||||
key={log.id ?? i}
|
||||
className="admin-badge"
|
||||
style={{
|
||||
fontSize: "0.7rem",
|
||||
@@ -95,7 +95,10 @@ function renderProjectCell(record: AttendanceRecord): React.ReactNode {
|
||||
background: isActive ? "var(--accent-light)" : undefined,
|
||||
}}
|
||||
>
|
||||
{log.project_name || `#${log.project_id}`} {durationValid ? `(${h}:${String(m).padStart(2, "0")}h${isActive ? " \u25B8" : ""})` : "—"}
|
||||
{log.project_name || `#${log.project_id}`}{" "}
|
||||
{durationValid
|
||||
? `(${h}:${String(m).padStart(2, "0")}h${isActive ? " \u25B8" : ""})`
|
||||
: "—"}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
@@ -257,4 +260,3 @@ export default function AttendanceShiftTable({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ interface BulkAttendanceUser {
|
||||
}
|
||||
|
||||
interface BulkAttendanceModalProps {
|
||||
show: boolean;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
form: BulkAttendanceForm;
|
||||
setForm: (form: BulkAttendanceForm) => void;
|
||||
@@ -29,7 +29,7 @@ interface BulkAttendanceModalProps {
|
||||
}
|
||||
|
||||
export default function BulkAttendanceModal({
|
||||
show,
|
||||
isOpen,
|
||||
onClose,
|
||||
form,
|
||||
setForm,
|
||||
@@ -39,11 +39,11 @@ export default function BulkAttendanceModal({
|
||||
toggleUser,
|
||||
toggleAllUsers,
|
||||
}: BulkAttendanceModalProps) {
|
||||
useModalLock(show);
|
||||
useModalLock(isOpen);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{show && (
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
className="admin-modal-overlay"
|
||||
initial={{ opacity: 0 }}
|
||||
|
||||
@@ -14,6 +14,71 @@ interface ConfirmModalProps {
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
function ConfirmIcon({ type }: { type: string }) {
|
||||
switch (type) {
|
||||
case "danger":
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<line x1="15" y1="9" x2="9" y2="15" />
|
||||
<line x1="9" y1="9" x2="15" y2="15" />
|
||||
</svg>
|
||||
);
|
||||
case "info":
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<line x1="12" y1="16" x2="12" y2="12" />
|
||||
<line x1="12" y1="8" x2="12.01" y2="8" />
|
||||
</svg>
|
||||
);
|
||||
case "warning":
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
||||
<line x1="12" y1="9" x2="12" y2="13" />
|
||||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||||
</svg>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<line x1="12" y1="16" x2="12" y2="12" />
|
||||
<line x1="12" y1="8" x2="12.01" y2="8" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default function ConfirmModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
@@ -49,18 +114,7 @@ export default function ConfirmModal({
|
||||
>
|
||||
<div className="admin-modal-body admin-confirm-content">
|
||||
<div className={`admin-confirm-icon admin-confirm-icon-${type}`}>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
||||
<line x1="12" y1="9" x2="12" y2="13" />
|
||||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||||
</svg>
|
||||
<ConfirmIcon type={type} />
|
||||
</div>
|
||||
<h2 id="confirm-modal-title" className="admin-confirm-title">
|
||||
{title}
|
||||
|
||||
@@ -68,7 +68,7 @@ interface ProjectLogRowProps {
|
||||
|
||||
export interface ShiftFormModalProps {
|
||||
mode: "create" | "edit";
|
||||
show: boolean;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: () => void;
|
||||
form: ShiftFormData;
|
||||
@@ -196,7 +196,7 @@ function ProjectLogRow({
|
||||
|
||||
export default function ShiftFormModal({
|
||||
mode,
|
||||
show,
|
||||
isOpen,
|
||||
onClose,
|
||||
onSubmit,
|
||||
form,
|
||||
@@ -208,7 +208,7 @@ export default function ShiftFormModal({
|
||||
onShiftDateChange,
|
||||
editingRecord,
|
||||
}: ShiftFormModalProps) {
|
||||
useModalLock(show);
|
||||
useModalLock(isOpen);
|
||||
const isCreate = mode === "create";
|
||||
const isWorkType = form.leave_type === "work";
|
||||
|
||||
@@ -240,7 +240,7 @@ export default function ShiftFormModal({
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{show && (
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
className="admin-modal-overlay"
|
||||
initial={{ opacity: 0 }}
|
||||
|
||||
Reference in New Issue
Block a user