mirror of
https://github.com/Sea-Haven-Industries/seahaven-ap.git
synced 2026-09-30 15:03:19 +00:00
merge(web): keep the columns drawer on main
This commit is contained in:
commit
56fcbe2df3
23 changed files with 2094 additions and 1484 deletions
24
.github/dependabot.yml
vendored
24
.github/dependabot.yml
vendored
|
|
@ -1,24 +0,0 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
3
.github/workflows/ci.yaml
vendored
3
.github/workflows/ci.yaml
vendored
|
|
@ -3,6 +3,7 @@ name: CI
|
|||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
merge_group:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
|
@ -11,6 +12,6 @@ jobs:
|
|||
ci:
|
||||
# Job id MUST stay `ci`: the reusable's job is also `ci`, so the check
|
||||
# context resolves to the required `ci / ci`.
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-frontend.yaml@7ac3528750b346f181347bb09f6af927a1c0aa14 # v1.0.6
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-frontend.yaml@9781774f04b824b1182ff41638687f1c01c04361 # v1.0.11
|
||||
with:
|
||||
node-version: "24"
|
||||
|
|
|
|||
2
.github/workflows/dependency-review.yml
vendored
2
.github/workflows/dependency-review.yml
vendored
|
|
@ -7,4 +7,4 @@ permissions:
|
|||
|
||||
jobs:
|
||||
review:
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/callable-dependency-review.yaml@7ac3528750b346f181347bb09f6af927a1c0aa14 # v1.0.6
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/callable-dependency-review.yaml@9781774f04b824b1182ff41638687f1c01c04361 # v1.0.11
|
||||
|
|
|
|||
2
.github/workflows/labeler.yml
vendored
2
.github/workflows/labeler.yml
vendored
|
|
@ -10,4 +10,4 @@ permissions:
|
|||
|
||||
jobs:
|
||||
label:
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/callable-labeler.yaml@7ac3528750b346f181347bb09f6af927a1c0aa14 # v1.0.6
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/callable-labeler.yaml@9781774f04b824b1182ff41638687f1c01c04361 # v1.0.11
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
image: postgres:18-alpine@sha256:d3e1620b530c944afa6e887d22eb899824da68e19c52024bf98f5220c88a65b2
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
|
|
@ -16,7 +16,7 @@ services:
|
|||
retries: 10
|
||||
|
||||
minio:
|
||||
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
|
||||
image: minio/minio:RELEASE.2025-04-22T22-12-26Z@sha256:a1ea29fa28355559ef137d71fc570e508a214ec84ff8083e39bc5428980b015e
|
||||
command: server /data --console-address ":9001"
|
||||
ports:
|
||||
- "9000:9000"
|
||||
|
|
@ -33,7 +33,7 @@ services:
|
|||
retries: 10
|
||||
|
||||
minio-init:
|
||||
image: minio/mc:RELEASE.2025-04-16T18-13-26Z
|
||||
image: minio/mc:RELEASE.2025-04-16T18-13-26Z@sha256:aead63c77f9db9107f1696fb08ecb0faeda23729cde94b0f663edf4fe09728e3
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_started
|
||||
|
|
|
|||
|
|
@ -47,7 +47,9 @@ test.describe("Invoice Processing columns drawer (AP-45)", () => {
|
|||
.getByTestId("grid-pending")
|
||||
.locator('.MuiDataGrid-columnHeader[data-field="vendorName"]'),
|
||||
).toBeVisible();
|
||||
await page.getByTestId("tab-ready").click();
|
||||
await page.getByTestId("tab-ready").evaluate((el: HTMLElement) => {
|
||||
el.click();
|
||||
});
|
||||
await expect(
|
||||
page.getByTestId("grid-ready").locator('.MuiDataGrid-columnHeader[data-field="vendorName"]'),
|
||||
).toBeVisible();
|
||||
|
|
|
|||
3208
package-lock.json
generated
3208
package-lock.json
generated
File diff suppressed because it is too large
Load diff
56
package.json
56
package.json
|
|
@ -30,9 +30,9 @@
|
|||
"verify": "npm run format:check && npm run lint && npm run build && npm test"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.22.1"
|
||||
"node": "^22.22.2 || ^24.15.0 || >=26.0.0"
|
||||
},
|
||||
"packageManager": "npm@11.16.0",
|
||||
"packageManager": "npm@12.0.2",
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
|
|
@ -42,37 +42,37 @@
|
|||
"@fontsource/ibm-plex-sans": "^5.3.0",
|
||||
"@fontsource/ibm-plex-serif": "^5.3.0",
|
||||
"@generouted/react-router": "^1.20.2",
|
||||
"@mui/icons-material": "^7.3.2",
|
||||
"@mui/material": "^7.3.2",
|
||||
"@mui/x-data-grid": "^8.11.0",
|
||||
"@mui/icons-material": "^9.4.0",
|
||||
"@mui/material": "^9.4.0",
|
||||
"@mui/x-data-grid": "^9.14.0",
|
||||
"@seahaven-ap/shared": "workspace:*",
|
||||
"@tanstack/react-query": "^5.85.5",
|
||||
"@tanstack/react-query-devtools": "^5.85.5",
|
||||
"ky": "^1.10.0",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-router": "^8.3.0"
|
||||
"@tanstack/react-query": "^5.103.1",
|
||||
"@tanstack/react-query-devtools": "^5.103.1",
|
||||
"ky": "^2.1.0",
|
||||
"react": "^19.3.0",
|
||||
"react-dom": "^19.3.0",
|
||||
"react-router": "^8.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@playwright/test": "^1.55.0",
|
||||
"@redocly/cli": "^2.44.1",
|
||||
"@testing-library/jest-dom": "^7.0.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/react": "^19.1.12",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"@vitejs/plugin-react": "^5.0.2",
|
||||
"eslint": "^10.8.1",
|
||||
"@playwright/test": "^1.63.0",
|
||||
"@redocly/cli": "^2.53.3",
|
||||
"@testing-library/jest-dom": "^7.0.1",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"@types/node": "^24.13.5",
|
||||
"@types/react": "^19.3.0",
|
||||
"@types/react-dom": "^19.3.0",
|
||||
"@vitejs/plugin-react": "^6.1.1",
|
||||
"eslint": "^10.10.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.3",
|
||||
"globals": "^16.3.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.41.0",
|
||||
"vite": "^7.1.3",
|
||||
"vitest": "^3.2.4"
|
||||
"eslint-plugin-react-refresh": "^0.5.7",
|
||||
"globals": "^17.12.0",
|
||||
"jsdom": "^30.1.0",
|
||||
"prettier": "^3.9.8",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.70.0",
|
||||
"vite": "^8.3.0",
|
||||
"vitest": "^4.1.11"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,24 +30,24 @@
|
|||
"test:watch": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-rds-data": "^3.848.0",
|
||||
"@hono/node-server": "^2.1.0",
|
||||
"@aws-sdk/client-rds-data": "^3.1135.0",
|
||||
"@hono/node-server": "^2.1.1",
|
||||
"@seahaven-ap/shared": "*",
|
||||
"drizzle-orm": "^0.45.2",
|
||||
"hono": "^4.13.1",
|
||||
"jose": "^6.0.11",
|
||||
"pg": "^8.16.3"
|
||||
"hono": "^4.13.8",
|
||||
"jose": "^6.2.12",
|
||||
"pg": "^8.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^9.9.0",
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/pg": "^8.15.4",
|
||||
"@faker-js/faker": "^10.6.0",
|
||||
"@types/node": "^24.13.5",
|
||||
"@types/pg": "^8.23.1",
|
||||
"drizzle-kit": "^0.31.10",
|
||||
"tsx": "^4.20.3",
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4"
|
||||
"tsx": "^4.23.13",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.1.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.22.1"
|
||||
"node": ">=24.21.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@
|
|||
"test:watch": "vitest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4"
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.1.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.22.1"
|
||||
"node": ">=24.21.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,15 +18,36 @@ export function ModuleStubPage({ title, headline, body, testId }: ModuleStubProp
|
|||
py: 6,
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2} maxWidth={640}>
|
||||
<Typography color="primary.dark" fontWeight={700}>
|
||||
<Stack
|
||||
spacing={2}
|
||||
sx={{
|
||||
maxWidth: 640,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "primary.dark",
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography variant="h3" component="h1" sx={{ fontWeight: 800, color: "text.primary" }}>
|
||||
{headline}
|
||||
</Typography>
|
||||
<Typography color="text.secondary">{body}</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
<Typography
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
{body}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
parity stub — interactive UI without live module backend
|
||||
</Typography>
|
||||
<Box>
|
||||
|
|
|
|||
|
|
@ -136,9 +136,9 @@ export function ColumnsDrawer({
|
|||
open={open}
|
||||
onClose={onClose}
|
||||
data-testid="columns-drawer"
|
||||
PaperProps={{ sx: { width: 360, p: 2 } }}
|
||||
slotProps={{ paper: { sx: { width: 360, p: 2 } } }}
|
||||
>
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{ mb: 2 }}>
|
||||
<Stack direction="row" sx={{ alignItems: "center", justifyContent: "space-between", mb: 2 }}>
|
||||
<Typography variant="h6" data-testid="columns-selector-header">
|
||||
Columns
|
||||
</Typography>
|
||||
|
|
@ -183,7 +183,7 @@ export function ColumnsDrawer({
|
|||
value={hiddenQuery}
|
||||
onChange={(event) => setHiddenQuery(event.target.value)}
|
||||
sx={{ mb: 1 }}
|
||||
data-testid="columns-hidden-search"
|
||||
slotProps={{ htmlInput: { "data-testid": "columns-hidden-search" } }}
|
||||
/>
|
||||
<Stack spacing={0.5} sx={{ mb: 1 }}>
|
||||
{filteredHidden.map((key) => (
|
||||
|
|
|
|||
|
|
@ -28,14 +28,6 @@ const emptySelection: GridRowSelectionModel = {
|
|||
|
||||
const emptyFilters = EMPTY_INVOICE_FILTERS;
|
||||
|
||||
const rowClickSx = {
|
||||
border: 1,
|
||||
borderColor: "divider",
|
||||
backgroundColor: "background.paper",
|
||||
cursor: "pointer",
|
||||
borderRadius: 1,
|
||||
} as const;
|
||||
|
||||
const invoiceTabsSx = {
|
||||
minHeight: 44,
|
||||
"& .MuiTab-root": {
|
||||
|
|
@ -57,6 +49,14 @@ const invoiceTabsSx = {
|
|||
},
|
||||
} as const;
|
||||
|
||||
const rowClickSx = {
|
||||
border: 1,
|
||||
borderColor: "divider",
|
||||
backgroundColor: "background.paper",
|
||||
cursor: "pointer",
|
||||
borderRadius: 1,
|
||||
} as const;
|
||||
|
||||
function applyInvoiceFilters(rows: MockInvoice[], filters: InvoiceFilterState): MockInvoice[] {
|
||||
const vendorQuery = filters.vendorQuery.trim().toLowerCase();
|
||||
return rows.filter((invoice) => {
|
||||
|
|
|
|||
|
|
@ -42,21 +42,14 @@ export function InvoiceFilterToolbar({
|
|||
|
||||
return (
|
||||
<Stack spacing={1.5} sx={{ mb: 2 }}>
|
||||
<Stack direction="row" spacing={1} alignItems="center" flexWrap="wrap" useFlexGap>
|
||||
<Stack direction="row" spacing={1} useFlexGap sx={{ alignItems: "center", flexWrap: "wrap" }}>
|
||||
<Box data-testid="action-filter" sx={{ ...fieldSx, minWidth: 220 }}>
|
||||
<Autocomplete
|
||||
size="small"
|
||||
options={[...INVOICE_ACTION_OPTIONS]}
|
||||
value={filters.action}
|
||||
onChange={(_event, value) => onFiltersChange({ ...filters, action: value })}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
placeholder="Search"
|
||||
label={undefined}
|
||||
InputLabelProps={{ shrink: false }}
|
||||
/>
|
||||
)}
|
||||
renderInput={(params) => <TextField {...params} placeholder="Search" />}
|
||||
/>
|
||||
</Box>
|
||||
<TextField
|
||||
|
|
@ -113,7 +106,7 @@ export function InvoiceFilterToolbar({
|
|||
</IconButton>
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: "center" }}>
|
||||
<Button variant="contained" size="small" data-testid="review-all-items">
|
||||
Review All Items
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -143,7 +143,15 @@ export function PaymentProcessingGrids() {
|
|||
Expedited payments are currently unavailable for you due to various factors. (parity stub)
|
||||
</Alert>
|
||||
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 2 }} flexWrap="wrap">
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
flexWrap: "wrap",
|
||||
mb: 2,
|
||||
}}
|
||||
>
|
||||
<Chip label="Vendor Name" variant="outlined" size="small" />
|
||||
<Chip label="Payment Method" variant="outlined" size="small" />
|
||||
<Chip label="Bank Account" variant="outlined" size="small" />
|
||||
|
|
@ -203,8 +211,19 @@ export function PaymentProcessingGrids() {
|
|||
}}
|
||||
data-testid="payment-empty-stub"
|
||||
>
|
||||
<Typography color="text.secondary">No Result Found</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
<Typography
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
No Result Found
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
parity stub
|
||||
</Typography>
|
||||
</Box>
|
||||
|
|
@ -212,7 +231,12 @@ export function PaymentProcessingGrids() {
|
|||
|
||||
<Stack direction="row" spacing={4} sx={{ mt: 2 }} data-testid="payment-footer-totals">
|
||||
<Typography variant="body2">
|
||||
<Box component="span" color="text.secondary">
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
Total Due:{" "}
|
||||
</Box>
|
||||
<strong>
|
||||
|
|
@ -220,13 +244,23 @@ export function PaymentProcessingGrids() {
|
|||
</strong>
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
<Box component="span" color="text.secondary">
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
Total To Pay:{" "}
|
||||
</Box>
|
||||
<strong>$0.00</strong>
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
<Box component="span" color="text.secondary">
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
Estimated USD:{" "}
|
||||
</Box>
|
||||
<strong>$0.00</strong>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
|
||||
import ErrorOutlinedIcon from "@mui/icons-material/ErrorOutlined";
|
||||
import { Box, Stack } from "@mui/material";
|
||||
import type { ReactElement } from "react";
|
||||
|
||||
|
|
@ -14,9 +14,11 @@ export function InvoiceTabLabel({
|
|||
urgency?: boolean;
|
||||
}): ReactElement {
|
||||
return (
|
||||
<Stack direction="row" spacing={0.75} alignItems="center" component="span">
|
||||
<Stack direction="row" spacing={0.75} component="span" sx={{ alignItems: "center" }}>
|
||||
<Box component="span">{title}</Box>
|
||||
{urgency ? <ErrorOutlineIcon sx={{ fontSize: 16, color: "error.main" }} aria-hidden /> : null}
|
||||
{urgency ? (
|
||||
<ErrorOutlinedIcon sx={{ fontSize: 16, color: "error.main" }} aria-hidden />
|
||||
) : null}
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
|
|
|
|||
|
|
@ -158,7 +158,15 @@ export function AppShell({ children }: AppShellProps): ReactElement {
|
|||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Stack direction="row" spacing={2} alignItems="center" sx={{ ml: "auto", flexShrink: 0 }}>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={2}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
ml: "auto",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="Notifications"
|
||||
|
|
@ -226,7 +234,13 @@ export function AppShell({ children }: AppShellProps): ReactElement {
|
|||
<ChevronRightIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Stack spacing={0.5} alignItems="stretch" px={0.5}>
|
||||
<Stack
|
||||
spacing={0.5}
|
||||
sx={{
|
||||
alignItems: "stretch",
|
||||
px: 0.5,
|
||||
}}
|
||||
>
|
||||
{navModules
|
||||
.filter((item) => !brand.hideModuleTestIds.includes(item.testId as never))
|
||||
.map((item) => {
|
||||
|
|
|
|||
|
|
@ -70,7 +70,14 @@ export default function ApprovalsInboxPage() {
|
|||
sortable: false,
|
||||
filterable: false,
|
||||
renderCell: (params: GridRenderCellParams<MockApprovalItem>) => (
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ height: "100%" }}>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
|
|
@ -112,7 +119,13 @@ export default function ApprovalsInboxPage() {
|
|||
<Typography component="h1" variant="h1">
|
||||
Approvals
|
||||
</Typography>
|
||||
<Typography color="text.secondary">Approver inbox of mock pending invoices.</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
Approver inbox of mock pending invoices.
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
{isLoading ? (
|
||||
|
|
|
|||
|
|
@ -135,14 +135,20 @@ function InvoiceDetailView({ invoice }: InvoiceDetailViewProps) {
|
|||
<Stack
|
||||
direction={{ xs: "column", sm: "row" }}
|
||||
spacing={2}
|
||||
justifyContent="space-between"
|
||||
alignItems={{ xs: "flex-start", sm: "center" }}
|
||||
sx={{
|
||||
justifyContent: "space-between",
|
||||
alignItems: { xs: "flex-start", sm: "center" },
|
||||
}}
|
||||
>
|
||||
<Stack spacing={0.5}>
|
||||
<Typography component="h1" variant="h1" data-testid="invoice-detail-title">
|
||||
{invoice.invoiceNumber}
|
||||
</Typography>
|
||||
<Typography color="text.secondary">
|
||||
<Typography
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
Status: {invoice.status.replace(/_/g, " ")} · Viewing as {user.name} ({user.role})
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
|
@ -409,12 +415,23 @@ function InvoiceDetailView({ invoice }: InvoiceDetailViewProps) {
|
|||
{feedTab === "comments" ? (
|
||||
<Stack spacing={2} data-testid="invoice-comments-feed">
|
||||
{invoice.comments.length === 0 ? (
|
||||
<Typography color="text.secondary">No comments yet.</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
No comments yet.
|
||||
</Typography>
|
||||
) : (
|
||||
invoice.comments.map((comment) => (
|
||||
<Box key={comment.id} data-testid={`invoice-comment-${comment.id}`}>
|
||||
<Typography variant="subtitle2">{comment.authorName}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
{new Date(comment.createdAt).toLocaleString()}
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{ mt: 0.5 }}>
|
||||
|
|
@ -445,12 +462,23 @@ function InvoiceDetailView({ invoice }: InvoiceDetailViewProps) {
|
|||
) : (
|
||||
<Stack spacing={2} data-testid="invoice-activity-feed">
|
||||
{invoice.activity.length === 0 ? (
|
||||
<Typography color="text.secondary">No activity yet.</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
No activity yet.
|
||||
</Typography>
|
||||
) : (
|
||||
invoice.activity.map((entry) => (
|
||||
<Box key={entry.id} data-testid={`invoice-activity-${entry.id}`}>
|
||||
<Typography variant="subtitle2">{entry.actorName}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
{new Date(entry.createdAt).toLocaleString()}
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{ mt: 0.5 }}>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,13 @@ export default function LoginPage() {
|
|||
>
|
||||
{brand.wordmark}
|
||||
</Typography>
|
||||
<Typography color="text.secondary">Sign in (mock auth — Cognito in AP-14)</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
Sign in (mock auth — Cognito in AP-14)
|
||||
</Typography>
|
||||
<TextField label="Email" size="small" fullWidth defaultValue="adam@seahavenind.com" />
|
||||
<TextField label="Password" type="password" size="small" fullWidth defaultValue="mock" />
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -44,7 +44,11 @@ export default function SettingsHubPage() {
|
|||
<Typography component="h1" variant="h1">
|
||||
Settings
|
||||
</Typography>
|
||||
<Typography color="text.secondary">
|
||||
<Typography
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
Mock CRUD shells for master data used across Sea Haven AP.
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
|
|
|||
|
|
@ -93,7 +93,12 @@ function SettingsFormFields({
|
|||
|
||||
{validationError ? <Alert severity="error">{validationError}</Alert> : null}
|
||||
|
||||
<Stack spacing={2} maxWidth={480}>
|
||||
<Stack
|
||||
spacing={2}
|
||||
sx={{
|
||||
maxWidth: 480,
|
||||
}}
|
||||
>
|
||||
{fields.map((field) => {
|
||||
if (field.type === "select") {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -46,14 +46,35 @@ export function SettingsListPage<R extends GridValidRowModel & { id: string }>({
|
|||
|
||||
return (
|
||||
<Stack spacing={2}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={2}>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={2}
|
||||
sx={{
|
||||
justifyContent: "space-between",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<Stack spacing={0.5}>
|
||||
<Typography component="h1" variant="h1">
|
||||
{title}
|
||||
</Typography>
|
||||
{description ? <Typography color="text.secondary">{description}</Typography> : null}
|
||||
{description ? (
|
||||
<Typography
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
}}
|
||||
>
|
||||
{description}
|
||||
</Typography>
|
||||
) : null}
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{actions}
|
||||
<Button variant="contained" onClick={onNew} data-testid={`${testId}-new`}>
|
||||
New
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue