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:
BOHA
2026-04-24 08:45:37 +02:00
parent 4f4b12f039
commit aa6c1b5094
35 changed files with 466 additions and 206 deletions

View File

@@ -119,6 +119,8 @@ export default function OrderDetail() {
const [deleteFiles, setDeleteFiles] = useState(false);
const [showConfirmationModal, setShowConfirmationModal] = useState(false);
const [confirmationLoading, setConfirmationLoading] = useState(false);
const initialNotesRef = useRef<string | null>(null);
const hasSetInitialSnapshot = useRef(false);
const fetchDetail = useCallback(async () => {
try {
@@ -144,6 +146,32 @@ export default function OrderDetail() {
fetchDetail();
}, [fetchDetail]);
useEffect(() => {
if (loading) {
hasSetInitialSnapshot.current = false;
return;
}
if (!hasSetInitialSnapshot.current) {
initialNotesRef.current = notes;
hasSetInitialSnapshot.current = true;
}
}, [loading, notes]);
const isDirty = useMemo(() => {
if (!initialNotesRef.current) return false;
return notes !== initialNotesRef.current;
}, [notes]);
useEffect(() => {
if (!isDirty) return;
const handler = (e: BeforeUnloadEvent) => {
e.preventDefault();
e.returnValue = "";
};
window.addEventListener("beforeunload", handler);
return () => window.removeEventListener("beforeunload", handler);
}, [isDirty]);
const totals = useMemo(() => {
if (!order?.items) return { subtotal: 0, vatAmount: 0, total: 0 };
const subtotal = order.items.reduce((sum, item) => {
@@ -197,6 +225,7 @@ export default function OrderDetail() {
const result = await response.json();
if (result.success) {
alert.success("Poznámky byly uloženy");
initialNotesRef.current = notes;
} else {
alert.error(result.error || "Nepodařilo se uložit poznámky");
}