Add RecordModal: row-click detail popup with inline editing
- Click any table row to open a full-detail popup - Editable fields (text, textarea, select, number) with PATCH save - Read-only display for IDs, timestamps, arrays - Pictures table fetches words via /pictures/:id/words in modal - Stop propagation on linked-field chips so they don't trigger modal - apiPatch + apiFetchOne helpers in api.js - editableFields + fetchRelated config in tables.js Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import Layout from '../components/Layout';
|
||||
import RecordModal from '../components/RecordModal';
|
||||
import { fetchAll } from '../lib/api';
|
||||
import { TABLES, STATUS_COLORS } from '../lib/tables';
|
||||
|
||||
@@ -22,7 +23,7 @@ function CellValue({ col, value, linkedFields, navigate }) {
|
||||
{value.slice(0, 3).map(id => (
|
||||
<button
|
||||
key={id}
|
||||
onClick={() => targetTable && navigate(`/db/${targetTable}?id=${id}`)}
|
||||
onClick={e => { e.stopPropagation(); targetTable && navigate(`/db/${targetTable}?id=${id}`); }}
|
||||
className="text-xs bg-indigo-50 text-indigo-600 rounded px-1.5 py-0.5 hover:bg-indigo-100 font-mono"
|
||||
title={String(id)}
|
||||
>
|
||||
@@ -41,7 +42,7 @@ function CellValue({ col, value, linkedFields, navigate }) {
|
||||
const targetTable = linkedFields[col];
|
||||
return (
|
||||
<button
|
||||
onClick={() => navigate(`/db/${targetTable}?id=${value}`)}
|
||||
onClick={e => { e.stopPropagation(); navigate(`/db/${targetTable}?id=${value}`); }}
|
||||
className="text-xs bg-indigo-50 text-indigo-600 rounded px-1.5 py-0.5 hover:bg-indigo-100 font-mono"
|
||||
title={value}
|
||||
>
|
||||
@@ -84,6 +85,7 @@ export default function TableView() {
|
||||
const [statusFilter, setStatusFilter] = useState('');
|
||||
const [textFilter, setTextFilter] = useState('');
|
||||
const [highlightId, setHighlightId] = useState(null);
|
||||
const [modalRecord, setModalRecord] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
@@ -118,6 +120,10 @@ export default function TableView() {
|
||||
});
|
||||
}, [rows, statusFilter, textFilter]);
|
||||
|
||||
function handleRecordSaved(updated) {
|
||||
setRows(prev => prev.map(r => (r.id === updated.id ? { ...r, ...updated } : r)));
|
||||
}
|
||||
|
||||
if (!meta) return <Layout back="/db"><p className="text-red-500">Unbekannte Tabelle: {tableKey}</p></Layout>;
|
||||
|
||||
return (
|
||||
@@ -191,7 +197,8 @@ export default function TableView() {
|
||||
{filtered.map((row, i) => (
|
||||
<tr
|
||||
key={row.id || i}
|
||||
className={`border-b border-slate-100 hover:bg-slate-50 transition-colors
|
||||
onClick={() => setModalRecord(row)}
|
||||
className={`border-b border-slate-100 hover:bg-slate-50 transition-colors cursor-pointer
|
||||
${highlightId && row.id === highlightId ? 'bg-indigo-50 ring-1 ring-indigo-300' : ''}`}
|
||||
>
|
||||
{meta.columns.map(col => (
|
||||
@@ -210,6 +217,15 @@ export default function TableView() {
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{modalRecord && (
|
||||
<RecordModal
|
||||
record={modalRecord}
|
||||
meta={meta}
|
||||
onClose={() => setModalRecord(null)}
|
||||
onSaved={handleRecordSaved}
|
||||
/>
|
||||
)}
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user