feat: add Config List page
This commit is contained in:
81
frontend/src/routes/ConfigList.tsx
Normal file
81
frontend/src/routes/ConfigList.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import Layout from '../components/Layout'
|
||||||
|
import DataTable, { Column } from '../components/DataTable'
|
||||||
|
import EntityForm from '../components/EntityForm'
|
||||||
|
import Box from '@mui/material/Box'
|
||||||
|
import Button from '@mui/material/Button'
|
||||||
|
import Chip from '@mui/material/Chip'
|
||||||
|
import AddIcon from '@mui/icons-material/Add'
|
||||||
|
import { configsApi } from '../api'
|
||||||
|
|
||||||
|
interface Config {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
is_active: boolean
|
||||||
|
created_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const COLUMNS: Column<Config>[] = [
|
||||||
|
{ key: 'name', label: 'Name' },
|
||||||
|
{ key: 'description', label: 'Description' },
|
||||||
|
{ key: 'is_active', label: 'Status', render: (r) => <Chip label={r.is_active ? 'Active' : 'Inactive'} color={r.is_active ? 'success' : 'default'} size="small" /> },
|
||||||
|
{ key: 'created_at', label: 'Created', render: (r) => new Date(r.created_at).toLocaleDateString() },
|
||||||
|
]
|
||||||
|
|
||||||
|
const FIELDS = [
|
||||||
|
{ name: 'name', label: 'Name', required: true },
|
||||||
|
{ name: 'description', label: 'Description' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function ConfigList() {
|
||||||
|
const [configs, setConfigs] = useState<Config[]>([])
|
||||||
|
const [formOpen, setFormOpen] = useState(false)
|
||||||
|
const [editing, setEditing] = useState<Config | null>(null)
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const load = () => configsApi.list().then((r) => setConfigs(r.data))
|
||||||
|
useEffect(() => { load() }, [])
|
||||||
|
|
||||||
|
const handleSubmit = async (values: Record<string, unknown>) => {
|
||||||
|
if (editing) {
|
||||||
|
await configsApi.update(editing.id, values)
|
||||||
|
} else {
|
||||||
|
await configsApi.create(values)
|
||||||
|
}
|
||||||
|
setFormOpen(false)
|
||||||
|
setEditing(null)
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = async (row: Config) => {
|
||||||
|
if (!confirm(`Delete config "${row.name}"?`)) return
|
||||||
|
await configsApi.delete(row.id)
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout title="Configurations">
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
||||||
|
<Button variant="contained" startIcon={<AddIcon />} onClick={() => { setEditing(null); setFormOpen(true) }}>
|
||||||
|
New Config
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<DataTable
|
||||||
|
columns={COLUMNS}
|
||||||
|
rows={configs}
|
||||||
|
onEdit={(row) => { navigate(`/configs/${row.id}`) }}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
/>
|
||||||
|
<EntityForm
|
||||||
|
open={formOpen}
|
||||||
|
title={editing ? 'Edit Config' : 'New Config'}
|
||||||
|
fields={FIELDS}
|
||||||
|
initialValues={editing ?? undefined}
|
||||||
|
onClose={() => { setFormOpen(false); setEditing(null) }}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user