merge(web): keep the filter toolbar on main

This commit is contained in:
Adam Moussa 2026-09-25 17:56:01 -04:00
commit af72fa31d1
No known key found for this signature in database
18 changed files with 2072 additions and 1459 deletions

View file

@ -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)"

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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

3208
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -30,46 +30,46 @@
"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": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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) => {

View file

@ -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 ? (

View file

@ -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 }}>

View file

@ -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

View file

@ -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>

View file

@ -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 (

View file

@ -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