AcadeDoc/apps/client/src/features/acadenice/database-view/components/row-detail-modal.tsx
Corentin 4cf04080cf fix(acadenice): resolve test suite failures across R3 sub-blocks (Patch 017)
- Convert 17 server spec files from vitest to Jest (vi -> jest globals)
- Add jest.mock stubs for ESM-only prosemirror/html and collaboration modules
- Fix Zod v4 strict UUID validation failures in test fixtures (version byte [1-8] required)
- Add JwtAuthGuard.overrideGuard in all controller specs that lacked it
- Fix jest.Mock type inference (ReturnType<typeof jest.fn> -> jest.Mock) to prevent 'never' arg errors
- Delete vitest.config.ts (CJS), keep vitest.config.mts (ESM-compatible) on client
- Add global mocks for @excalidraw/excalidraw and @/main.tsx in client test-setup
- Result: client 38/38 suites 313/313 tests, server acadenice 21/21 suites 210/210 tests, 0 TS errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 10:36:19 +02:00

108 lines
3.4 KiB
TypeScript

import { Modal, Stack, Text, Group, Badge, Divider, Tabs } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { useAtom } from "jotai";
import { currentUserAtom } from "@/features/user/atoms/current-user-atom";
import type { BridgeRow, BridgeField } from "../types/database-view.types";
import { RowCommentsPanel } from "@/features/acadenice/comments/components/row-comments-panel";
interface RowDetailModalProps {
row: BridgeRow | null;
fields: BridgeField[];
opened: boolean;
onClose: () => void;
}
/**
* Simple row detail modal — opened when the user clicks on a calendar event.
*
* Why simple in R3.1.d:
* Full inline editing inside the modal is a larger UX investment (field-level
* save, validation, optimistic feedback). The priority here is to make the
* calendar renderer clickable and show meaningful data. Inline edit from the
* modal is slated for R3.1.e / R3.2.
*/
export function RowDetailModal({ row, fields, opened, onClose }: RowDetailModalProps) {
const { t } = useTranslation();
const [currentUser] = useAtom(currentUserAtom);
if (!row) return null;
return (
<Modal
opened={opened}
onClose={onClose}
title={t("database_view.row_detail.title")}
size="lg"
centered
>
<Tabs defaultValue="fields">
<Tabs.List>
<Tabs.Tab value="fields">
{t("database_view.row_detail.tab_fields")}
</Tabs.Tab>
<Tabs.Tab value="comments">
{t("database_view.row_detail.tab_comments")}
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="fields" pt="xs">
<Stack gap="xs">
{fields.map((field) => {
const rawValue = row.fields[field.name] ?? row.fields[field.id];
return (
<div key={field.id}>
<Group gap="xs" mb={2}>
<Text size="xs" fw={600} c="dimmed">
{field.name}
</Text>
{field.primary && (
<Badge size="xs" variant="light" color="blue">
{t("database_view.row_detail.primary_badge")}
</Badge>
)}
</Group>
<Text size="sm">{formatValue(rawValue)}</Text>
<Divider mt="xs" />
</div>
);
})}
{fields.length === 0 && (
<Text size="sm" c="dimmed">
{t("database_view.row_detail.no_fields")}
</Text>
)}
</Stack>
</Tabs.Panel>
<Tabs.Panel value="comments" pt="xs">
{currentUser && (
<RowCommentsPanel
tableId={String(row.tableId ?? "")}
rowId={String(row.id)}
currentUserId={currentUser.user.id}
/>
)}
</Tabs.Panel>
</Tabs>
</Modal>
);
}
function formatValue(value: unknown): string {
if (value === null || value === undefined) return "—";
if (typeof value === "boolean") return value ? "true" : "false";
if (Array.isArray(value)) {
return value
.map((v) =>
typeof v === "object" && v !== null
? (v as { value?: string }).value ?? JSON.stringify(v)
: String(v),
)
.join(", ");
}
if (typeof value === "object") {
return (value as { value?: string }).value ?? JSON.stringify(value);
}
return String(value);
}