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:
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user