feat: NAS storage for invoices/offers, code cleanup, date/time fixes

- NAS storage for created invoices (PDF via puppeteer), received invoices,
  and offers with auto-save on create/edit
- Deterministic file paths derived from DB fields (no file_path column needed)
- Separate NAS mount points: NAS_FINANCIALS_PATH, NAS_OFFERS_PATH
- Invoice language field (cs/en) stored per invoice, replaces lang modal
- Invoices list filtered by month/year matching KPI card selection
- Centralized date helpers (src/utils/date.ts) replacing all .toISOString()
  calls that returned UTC instead of local time
- Attendance project switching uses exact time (not rounded)
- Comment cleanup: removed ~100 unnecessary/Czech comments
- Removed as-any casts in orders and attendance
- Prisma migrations: add invoice language, drop received_invoices BLOB columns

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-03-26 10:36:39 +01:00
parent 0317ba3168
commit baceb88347
60 changed files with 2475 additions and 563 deletions

View File

@@ -148,7 +148,6 @@ export default function ReceivedInvoices({
const { sort, order, handleSort, activeSort } = useTableSort("created_at");
const [search, setSearch] = useState("");
// Data
const [invoices, setInvoices] = useState<ReceivedInvoice[]>([]);
const [loading, setLoading] = useState(true);
const [stats, setStats] = useState<ReceivedStats | null>(null);
@@ -159,7 +158,6 @@ export default function ReceivedInvoices({
const prevMonth = useRef(statsMonth);
const prevYear = useRef(statsYear);
// Modals
const [editOpen, setEditOpen] = useState(false);
const [editInvoice, setEditInvoice] = useState<EditInvoice | null>(null);
const [deleteConfirm, setDeleteConfirm] = useState<{
@@ -169,10 +167,8 @@ export default function ReceivedInvoices({
const [deleting, setDeleting] = useState(false);
const [saving, setSaving] = useState(false);
// Supplier autocomplete
const [supplierNames, setSupplierNames] = useState<string[]>([]);
// Upload state
const [uploadFiles, setUploadFiles] = useState<File[]>([]);
const [uploadMeta, setUploadMeta] = useState<UploadMeta[]>([]);
const [uploadErrors, setUploadErrors] = useState<UploadErrors>({});
@@ -180,7 +176,6 @@ export default function ReceivedInvoices({
useModalLock(uploadOpen || editOpen);
// Slide direction detection
useEffect(() => {
const prev = prevYear.current * 12 + prevMonth.current;
const curr = statsYear * 12 + statsMonth;
@@ -194,7 +189,6 @@ export default function ReceivedInvoices({
prevYear.current = statsYear;
}, [statsMonth, statsYear]);
// Fetch list
const fetchList = useCallback(async () => {
if (!hasLoadedOnce.current) setLoading(true);
try {
@@ -228,7 +222,6 @@ export default function ReceivedInvoices({
fetchList();
}, [fetchList]);
// Fetch supplier names for autocomplete
useEffect(() => {
apiFetch(`${API_BASE}/received-invoices/suppliers`)
.then((r) => r.json())
@@ -277,7 +270,6 @@ export default function ReceivedInvoices({
load();
}, [statsMonth, statsYear]);
// Upload handlers
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
const selected = Array.from(e.target.files || []);
if (selected.length === 0) {
@@ -384,7 +376,6 @@ export default function ReceivedInvoices({
}
};
// Edit handlers
const toDateInput = (d: string | null | undefined): string => {
if (!d) return "";
const date = new Date(d);
@@ -455,7 +446,6 @@ export default function ReceivedInvoices({
}
};
// Delete
const handleDelete = async () => {
if (!deleteConfirm.invoice) {
return;
@@ -484,26 +474,20 @@ export default function ReceivedInvoices({
}
};
// View file
const openFile = async (inv: ReceivedInvoice) => {
const newWindow = window.open("", "_blank");
try {
const response = await apiFetch(
`${API_BASE}/received-invoices/${inv.id}/file`,
);
if (!response.ok) {
newWindow?.close();
alert.error("Nepodařilo se načíst soubor");
return;
}
const blob = await response.blob();
const url = URL.createObjectURL(blob);
if (newWindow) {
newWindow.location.href = url;
}
window.open(url, "_blank");
setTimeout(() => URL.revokeObjectURL(url), 60000);
} catch {
newWindow?.close();
alert.error("Chyba připojení");
}
};
@@ -531,7 +515,6 @@ export default function ReceivedInvoices({
const monthLabel = `${MONTH_NAMES[statsMonth - 1]}`;
// KPI
const renderKpi = () => {
if (!hasLoadedOnce.current && statsLoading) {
return (