style(fetch-requests): two glass panes on create page + matching list rows
- Source & Account / Window & Policy split into equal-height frosted panes (grid stretch, account/bank above source) - fixed full-viewport brand-indigo gradient backdrop so blur(8px) reads as glass - recent fetch-request rows adopt the same translucent recipe
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import ReceiptLongIcon from "@mui/icons-material/ReceiptLong";
|
import ReceiptLongIcon from "@mui/icons-material/ReceiptLong";
|
||||||
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
|
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
|
||||||
|
import { alpha } from "@mui/material/styles";
|
||||||
import { useAppContext, useResource, ListCellRenderer, FormFieldRenderer, getApi, applyDisplayFormat } from "../../react-openapi";
|
import { useAppContext, useResource, ListCellRenderer, FormFieldRenderer, getApi, applyDisplayFormat } from "../../react-openapi";
|
||||||
import type { FieldConfig } from "../../react-openapi";
|
import type { FieldConfig } from "../../react-openapi";
|
||||||
import { PageHeader } from "../ui/PageHeader";
|
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"];
|
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() {
|
function FetchRequestList() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { list, remove, resource } = useResource("fetch-requests");
|
const { list, remove, resource } = useResource("fetch-requests");
|
||||||
@@ -88,13 +116,18 @@ function FetchRequestList() {
|
|||||||
<Paper
|
<Paper
|
||||||
key={row.id ?? i}
|
key={row.id ?? i}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
sx={{
|
sx={(theme) => ({
|
||||||
p: 2,
|
p: 2,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
borderRadius: 2,
|
borderRadius: 3,
|
||||||
transition: "border-color 160ms ease, box-shadow 160ms ease",
|
transition: "border-color 160ms ease, box-shadow 160ms ease, background-color 160ms ease",
|
||||||
"&:hover": { borderColor: "primary.main", boxShadow: 1 },
|
"&:hover": {
|
||||||
}}
|
borderColor: "primary.main",
|
||||||
|
boxShadow: 1,
|
||||||
|
backgroundColor: alpha(theme.palette.background.default, 0.9),
|
||||||
|
},
|
||||||
|
...glassSx(theme),
|
||||||
|
})}
|
||||||
onClick={() => navigate(`/fetch-requests/${row.id}`)}
|
onClick={() => navigate(`/fetch-requests/${row.id}`)}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: "flex", gap: 2, flexWrap: "wrap", alignItems: "center" }}>
|
<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 (
|
return (
|
||||||
<Container maxWidth="lg" sx={{ py: 4 }}>
|
<Box sx={{ position: "relative" }}>
|
||||||
<PageHeader
|
{/* Decorative gradient backdrop — gives the frosted panes something to blur. */}
|
||||||
crumbs={[{ label: "Home", path: "/" }, { label: "Fetch Requests" }]}
|
<Box
|
||||||
title="Fetch Requests"
|
aria-hidden
|
||||||
subtitle="Import transactions from bank statements or email and track pipeline progress."
|
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
|
<Box id="new-fetch-request" sx={{ mb: 4 }}>
|
||||||
id="new-fetch-request"
|
<Typography variant="subtitle1" fontWeight={700} gutterBottom>
|
||||||
variant="outlined"
|
New Fetch Request
|
||||||
sx={{ p: 3, mb: 4, borderRadius: 3 }}
|
</Typography>
|
||||||
>
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2.5, maxWidth: 560, lineHeight: 1.6 }}>
|
||||||
<Typography variant="subtitle1" fontWeight={700} gutterBottom>
|
Choose an account, pipeline, and a file or email source to kick off an import.
|
||||||
New Fetch Request
|
</Typography>
|
||||||
</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 sx={{ display: "flex", flexDirection: "column", gap: 2, maxWidth: 520 }}>
|
<Box
|
||||||
{formFields.map((field) => (
|
sx={{
|
||||||
<FormFieldRenderer
|
display: "grid",
|
||||||
key={field.name}
|
gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" },
|
||||||
field={field}
|
gap: 2,
|
||||||
value={formData[field.name] ?? ""}
|
}}
|
||||||
onChange={(val) => handleChange(field.name, val)}
|
>
|
||||||
fkOptions={fkOptions[field.name]}
|
<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>
|
||||||
|
|
||||||
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 3, gap: 1.5 }}>
|
<Typography variant="h6" fontWeight={700} sx={{ mb: 2 }}>
|
||||||
<Button variant="outlined" onClick={() => { setFormData({}); setResult(null); }}>
|
Recent Fetch Requests
|
||||||
Reset
|
</Typography>
|
||||||
</Button>
|
<FetchRequestList />
|
||||||
<Button variant="contained" onClick={handleSubmit} disabled={loading} sx={{ minWidth: 180 }}>
|
</Container>
|
||||||
{loading ? <CircularProgress size={20} sx={{ mr: 0.5 }} /> : null}
|
</Box>
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user