feat(ui): redesign job workspace sections
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user