Files
ERP/frontend/src/components/FileList.tsx
T
MMD f7b1f7f751 feat(security): JWT required on every API route, gated dev fallback, role checks
- main.py: global get_current_user dependency on all routers except
  /auth/login and /health; prod fail-fast startup guard rejects wildcard
  CORS, default credentials, DEV_LOGIN_ENABLED
- inventory/pm: X-User-Role header identity fallback now only active when
  PROCUREMENT_DEV_LOGIN_ENABLED=true (was unconditional in production)
- item_decisions writes + attachment deletes: require_role(
  procurement_manager, admin) — require_role was defined but never used
- config.py: ENVIRONMENT flag, comma-separated CORS_ORIGINS, no default
  Casdoor client secret/admin password, DEV_LOGIN_ENABLED=false
- frontend: authenticated file downloads (Bearer + blob) — <a href> links
  cannot send the token; FileList/QuoteDetail/AcceptModal/EditModal/
  PurchaseHistory converted; DEV_LOGIN_ENABLED build arg defaults false
- smoke tests authenticate via POST /auth/login (SMOKE_USER/SMOKE_PASSWORD)
- traefik: security headers, API rate limit, 12MB body cap (prod overlay)
- nginx client_max_body_size aligned to 12MB
2026-09-18 22:02:09 +03:30

83 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useRef } from "react";
import { toast } from "sonner";
import type { Attachment } from "../lib/types";
export interface FileListProps {
files: Attachment[];
onChange: (files: Attachment[]) => void;
// آپلود واقعی هر فایل به سرور (باید Attachment با id برگرداند)
upload: (file: File) => Promise<Attachment>;
// حذف سمت سرور (اختیاری — بعد از حذف محلی صدا زده می‌شود)
onDelete?: (a: Attachment) => void;
// دانلود احراز هویت‌شده (توکن Bearer + blob — لینک خام توکن نمی‌فرستد)
onDownload?: (a: Attachment) => void | Promise<void>;
label?: string;
}
// مدیریت چند فاکتور: آپلود مستقیم به سرور (MinIO) + دانلود/حذف فایل‌های ذخیره‌شده
export function FileList({ files, onChange, upload, onDelete, onDownload, label = "افزودن فاکتور" }: FileListProps) {
const inputRef = useRef<HTMLInputElement | null>(null);
const remove = (i: number) => {
const removed = files[i];
onChange(files.filter((_, idx) => idx !== i));
if (removed) onDelete?.(removed);
};
const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const picked = Array.from(e.target.files || []);
e.target.value = ""; // اجازه‌ی انتخاب دوباره‌ی همان فایل
if (!picked.length) return;
const added: Attachment[] = [];
for (const file of picked) {
try {
added.push(await upload(file));
} catch (err) {
toast.error(err instanceof Error ? err.message : `آپلود «${file.name}» ناموفق بود`);
}
}
if (added.length) onChange([...files, ...added]);
};
return (
<div>
{!files.length && <div className="inv-empty">هنوز فایلی انتخاب نشده است.</div>}
{files.map((inv, i) => (
<div key={i} className="inv-row">
<span className="inv-name">📎 {inv.name}</span>
{onDownload && (
<button
type="button"
className="inv-open"
title="دانلود"
onClick={() => {
Promise.resolve(onDownload(inv)).catch((err) =>
toast.error(err instanceof Error ? err.message : "دانلود فایل ناموفق بود"),
);
}}
>
↓
</button>
)}
<button type="button" className="inv-x" title="حذف" onClick={() => remove(i)}>
×
</button>
</div>
))}
<div>
<input
ref={inputRef}
type="file"
accept="image/*,.pdf,.png,.jpg,.jpeg"
multiple
className="hidden"
onChange={handleChange}
/>
<button type="button" className="btn-line-file" onClick={() => inputRef.current?.click()}>
+ {label}
</button>
</div>
</div>
);
}