Fetch-request source types, glass-pane create page, date formatting #18
@@ -6,6 +6,7 @@ import {
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import ReceiptLongIcon from "@mui/icons-material/ReceiptLong";
|
||||
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
|
||||
import { alpha } from "@mui/material/styles";
|
||||
import { useAppContext, useResource, ListCellRenderer, FormFieldRenderer, getApi, applyDisplayFormat } from "../../react-openapi";
|
||||
import type { FieldConfig } from "../../react-openapi";
|
||||
import { PageHeader } from "../ui/PageHeader";
|
||||
@@ -15,6 +16,33 @@ import { useToast } from "../ui/Toast";
|
||||
|
||||
const CREATE_FIELDS = ["account", "bank", "pipeline", "trust_fallback", "start_date", "end_date", "source"];
|
||||
|
||||
// Two-pane form partition — explicit names, spec order is unreliable.
|
||||
// Grid stretch keeps both panes equal-height regardless of which source
|
||||
// variant is active (email adds From/Subject under the type select).
|
||||
const SOURCE_PANE_FIELDS = ["account", "bank"];
|
||||
const POLICY_PANE_FIELDS = ["start_date", "end_date", "pipeline", "trust_fallback"];
|
||||
|
||||
const glassSx = (theme: any) => ({
|
||||
backgroundColor: alpha(theme.palette.background.default, 0.72),
|
||||
backdropFilter: "blur(8px)",
|
||||
borderColor: "divider",
|
||||
boxShadow: 1,
|
||||
});
|
||||
|
||||
function GlassPanel({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<Paper variant="outlined" sx={(theme) => ({ p: 3, borderRadius: 3, ...glassSx(theme) })}>
|
||||
<Typography
|
||||
variant="overline"
|
||||
sx={{ display: "block", mb: 2, color: "text.secondary", letterSpacing: 1.2, lineHeight: 1.4 }}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>{children}</Box>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
function FetchRequestList() {
|
||||
const navigate = useNavigate();
|
||||
const { list, remove, resource } = useResource("fetch-requests");
|
||||
@@ -88,13 +116,18 @@ function FetchRequestList() {
|
||||
<Paper
|
||||
key={row.id ?? i}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
sx={(theme) => ({
|
||||
p: 2,
|
||||
cursor: "pointer",
|
||||
borderRadius: 2,
|
||||
transition: "border-color 160ms ease, box-shadow 160ms ease",
|
||||
"&:hover": { borderColor: "primary.main", boxShadow: 1 },
|
||||
}}
|
||||
borderRadius: 3,
|
||||
transition: "border-color 160ms ease, box-shadow 160ms ease, background-color 160ms ease",
|
||||
"&:hover": {
|
||||
borderColor: "primary.main",
|
||||
boxShadow: 1,
|
||||
backgroundColor: alpha(theme.palette.background.default, 0.9),
|
||||
},
|
||||
...glassSx(theme),
|
||||
})}
|
||||
onClick={() => navigate(`/fetch-requests/${row.id}`)}
|
||||
>
|
||||
<Box sx={{ display: "flex", gap: 2, flexWrap: "wrap", alignItems: "center" }}>
|
||||
@@ -207,59 +240,90 @@ export default function FetchRequestCreate() {
|
||||
);
|
||||
}
|
||||
|
||||
const renderPaneFields = (names: string[]) =>
|
||||
names.map((name) => {
|
||||
const field = formFields.find((f) => f.name === name);
|
||||
if (!field) return null;
|
||||
return (
|
||||
<FormFieldRenderer
|
||||
key={field.name}
|
||||
field={field}
|
||||
value={formData[field.name] ?? ""}
|
||||
onChange={(val) => handleChange(field.name, val)}
|
||||
fkOptions={fkOptions[field.name]}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Container maxWidth="lg" sx={{ py: 4 }}>
|
||||
<PageHeader
|
||||
crumbs={[{ label: "Home", path: "/" }, { label: "Fetch Requests" }]}
|
||||
title="Fetch Requests"
|
||||
subtitle="Import transactions from bank statements or email and track pipeline progress."
|
||||
<Box sx={{ position: "relative" }}>
|
||||
{/* Decorative gradient backdrop — gives the frosted panes something to blur. */}
|
||||
<Box
|
||||
aria-hidden
|
||||
sx={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
zIndex: 0,
|
||||
pointerEvents: "none",
|
||||
background: (theme) =>
|
||||
[
|
||||
`radial-gradient(640px circle at 12% 8%, ${alpha(theme.palette.primary.main, 0.14)}, transparent 70%)`,
|
||||
`radial-gradient(900px circle at 88% 92%, ${alpha(theme.palette.primary.main, 0.09)}, transparent 70%)`,
|
||||
`radial-gradient(520px circle at 78% 22%, ${alpha(theme.palette.primary.light, 0.07)}, transparent 70%)`,
|
||||
].join(", "),
|
||||
}}
|
||||
/>
|
||||
<Container maxWidth="lg" sx={{ py: 4, position: "relative", zIndex: 1 }}>
|
||||
<PageHeader
|
||||
crumbs={[{ label: "Home", path: "/" }, { label: "Fetch Requests" }]}
|
||||
title="Fetch Requests"
|
||||
subtitle="Import transactions from bank statements or email and track pipeline progress."
|
||||
/>
|
||||
|
||||
<Paper
|
||||
id="new-fetch-request"
|
||||
variant="outlined"
|
||||
sx={{ p: 3, mb: 4, borderRadius: 3 }}
|
||||
>
|
||||
<Typography variant="subtitle1" fontWeight={700} gutterBottom>
|
||||
New Fetch Request
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3, maxWidth: 520, lineHeight: 1.6 }}>
|
||||
Choose an account, pipeline, and a file or email source to kick off an import.
|
||||
</Typography>
|
||||
<Box id="new-fetch-request" sx={{ mb: 4 }}>
|
||||
<Typography variant="subtitle1" fontWeight={700} gutterBottom>
|
||||
New Fetch Request
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2.5, maxWidth: 560, lineHeight: 1.6 }}>
|
||||
Choose an account, pipeline, and a file or email source to kick off an import.
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 2, maxWidth: 520 }}>
|
||||
{formFields.map((field) => (
|
||||
<FormFieldRenderer
|
||||
key={field.name}
|
||||
field={field}
|
||||
value={formData[field.name] ?? ""}
|
||||
onChange={(val) => handleChange(field.name, val)}
|
||||
fkOptions={fkOptions[field.name]}
|
||||
/>
|
||||
))}
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" },
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<GlassPanel title="Source & Account">
|
||||
{renderPaneFields(SOURCE_PANE_FIELDS)}
|
||||
{renderPaneFields(["source"])}
|
||||
</GlassPanel>
|
||||
<GlassPanel title="Window & Policy">{renderPaneFields(POLICY_PANE_FIELDS)}</GlassPanel>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 3, gap: 1.5 }}>
|
||||
<Button variant="outlined" onClick={() => { setFormData({}); setResult(null); }}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button variant="contained" onClick={handleSubmit} disabled={loading} sx={{ minWidth: 180 }}>
|
||||
{loading ? <CircularProgress size={20} sx={{ mr: 0.5 }} /> : null}
|
||||
Create Fetch Request
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{result && (
|
||||
<Alert severity={result.severity} sx={{ mt: 2 }} onClose={() => setResult(null)}>
|
||||
{result.message}
|
||||
</Alert>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 3, gap: 1.5 }}>
|
||||
<Button variant="outlined" onClick={() => { setFormData({}); setResult(null); }}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button variant="contained" onClick={handleSubmit} disabled={loading} sx={{ minWidth: 180 }}>
|
||||
{loading ? <CircularProgress size={20} sx={{ mr: 0.5 }} /> : null}
|
||||
Create Fetch Request
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{result && (
|
||||
<Alert severity={result.severity} sx={{ mt: 2 }} onClose={() => setResult(null)}>
|
||||
{result.message}
|
||||
</Alert>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
<Typography variant="h6" fontWeight={700} sx={{ mb: 2 }}>
|
||||
Recent Fetch Requests
|
||||
</Typography>
|
||||
<FetchRequestList />
|
||||
</Container>
|
||||
<Typography variant="h6" fontWeight={700} sx={{ mb: 2 }}>
|
||||
Recent Fetch Requests
|
||||
</Typography>
|
||||
<FetchRequestList />
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user