feat(ui): redesign job workspace sections

This commit is contained in:
cesnimda
2026-10-07 01:58:31 +02:00
parent acd1d94684
commit ac6d78289d
123 changed files with 2842 additions and 205 deletions
+20 -20
View File
@@ -35,6 +35,7 @@ import { api, getApiErrorMessage } from "../api";
import { useToast } from "../toast";
import { useI18n } from "../i18n/I18nProvider";
import { useDialogActions } from "../dialogs";
import ViewStateNotice from "./ViewStateNotice";
interface AttachmentItem {
id: number;
@@ -85,9 +86,11 @@ function purposeLabel(purpose: string | null | undefined, t: (key: any) => strin
export default function Attachments({ jobId }: { jobId: number }) {
const labelId = useId();
const { toast } = useToast();
const { t } = useI18n();
const { language, t } = useI18n();
const { confirmAction, promptForValue } = useDialogActions();
const [items, setItems] = useState<AttachmentItem[]>([]);
const [loadError, setLoadError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [previewOpen, setPreviewOpen] = useState(false);
const [preview, setPreview] = useState<{ url: string; type: string; name: string } | null>(null);
const [dragActive, setDragActive] = useState(false);
@@ -95,13 +98,17 @@ export default function Attachments({ jobId }: { jobId: number }) {
const fileInputRef = useRef<HTMLInputElement | null>(null);
const load = useCallback(async () => {
setLoading(true);
try {
const res = await api.get<AttachmentItem[]>(`/attachments/${jobId}`);
setItems(res.data);
} catch {
// non-fatal
setLoadError(null);
} catch (error) {
setLoadError(getApiErrorMessage(error, t("attachmentsLoadFailed")));
} finally {
setLoading(false);
}
}, [jobId]);
}, [jobId, t]);
useEffect(() => {
void load();
@@ -253,11 +260,10 @@ export default function Attachments({ jobId }: { jobId: number }) {
sx={{
mb: 2,
p: 2,
borderRadius: 3,
borderRadius: 1,
border: "1px dashed",
borderColor: dragActive ? "primary.main" : "divider",
backgroundColor: dragActive ? "action.hover" : "background.paper",
transition: "all 0.2s ease",
textAlign: "center",
}}
>
@@ -268,14 +274,17 @@ export default function Attachments({ jobId }: { jobId: number }) {
{uploading ? <LinearProgress sx={{ mt: 1.5, borderRadius: 999 }} /> : null}
</Box>
<TableContainer sx={{ borderRadius: 3, border: "none", boxShadow: "0px 1px 2px 0px rgba(15,23,42,0.04), 0px 8px 24px -12px rgba(15,23,42,0.12)" }}>
<Table size="small">
<ViewStateNotice title={t("attachmentsLoadFailed")} loading={loading} error={loadError ? { kind: "error", message: loadError, retryable: true } : null} onRetry={load} compact />
{!loading && !loadError && items.length === 0 && <Alert severity="info" variant="outlined">{t("attachmentsEmpty")}</Alert>}
{items.length > 0 && <TableContainer role="region" tabIndex={0} aria-label={t("attachmentsTitle", { count })} sx={{ maxWidth: "100%", borderRadius: 1, border: 1, borderColor: "divider", overflowX: "auto" }}>
<Table size="small" aria-label={t("workspaceCurrentDocuments")} sx={{ minWidth: 900 }}>
<TableHead>
<TableRow>
<TableCell>{t("attachmentsName")}</TableCell>
<TableCell sx={{ width: 140 }}>{t("attachmentsKind")}</TableCell>
<TableCell sx={{ width: 120 }}>{t("attachmentsType")}</TableCell>
<TableCell sx={{ width: 90 }}>{t("attachmentsSize")}</TableCell>
<TableCell>{t("attachmentsUploaded")}</TableCell>
<TableCell sx={{ width: 160 }}>{t("attachmentsPurpose")}</TableCell>
<TableCell sx={{ width: 110 }}>{t("attachmentsAiUse")}</TableCell>
<TableCell sx={{ width: 170 }}>{t("attachmentsActions")}</TableCell>
@@ -288,14 +297,14 @@ export default function Attachments({ jobId }: { jobId: number }) {
return (
<TableRow key={a.id} hover>
<TableCell>
<Button size="small" variant="text" onClick={() => void download(a)} sx={{ fontWeight: 800, textTransform: "none" }}>
<Button size="small" variant="text" onClick={() => void download(a)} sx={{ fontWeight: 700, textTransform: "none", textAlign: "left", overflowWrap: "anywhere" }}>
{a.fileName}
</Button>
</TableCell>
<TableCell>{kind}</TableCell>
<TableCell sx={{ color: "text.secondary" }}>{a.fileType ? a.fileType.replace("application/", "") : ""}</TableCell>
<TableCell sx={{ color: "text.secondary" }}>{a.fileSize ? fmtSize(a.fileSize) : ""}</TableCell>
<TableCell sx={{ color: "text.secondary" }}>{a.uploadDate ? new Date(a.uploadDate).toLocaleString() : ""}</TableCell>
<TableCell sx={{ color: "text.secondary" }}>{a.uploadDate ? new Date(a.uploadDate).toLocaleString(language === "nb" ? "nb-NO" : "en") : ""}</TableCell>
<TableCell>
<FormControl size="small" fullWidth>
<InputLabel id={`${labelId}-${a.id}`}>{t("attachmentsPurpose")}</InputLabel>
@@ -337,18 +346,9 @@ export default function Attachments({ jobId }: { jobId: number }) {
);
})}
{items.length === 0 ? (
<TableRow>
<TableCell colSpan={8}>
<Alert severity="info" variant="outlined">
{t("attachmentsEmpty")}
</Alert>
</TableCell>
</TableRow>
) : null}
</TableBody>
</Table>
</TableContainer>
</TableContainer>}
<Dialog open={previewOpen} onClose={() => setPreviewOpen(false)} fullWidth maxWidth="md">
<DialogTitle>{preview?.name ? t("attachmentsPreviewTitle", { name: preview.name }) : t("attachmentsPreview")}</DialogTitle>