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