All pages under `(main)` must share the same look and feel. Use this as the single source of truth for new and existing pages.
### Stack & layout
- **Styling:** Tailwind CSS for layout and utilities. MUI components are used with the project theme (primary blue, neutral borders) so they match the standard.
- **Page wrapper:** Do **not** add a full-page wrapper with its own background or padding. The main layout (`src/app/(main)/layout.tsx`) already provides:
- **Design tokens** are in `src/app/global.css` (`:root` / `.dark`): `--primary`, `--accent`, `--background`, `--foreground`, `--card`, `--border`, `--muted`. Use these in custom CSS or Tailwind when you need to stay in sync.
### Page structure (every page)
1. **Page title bar (consistent across all pages):**
- Use the shared **PageTitleBar** component from `@/components/PageTitleBar` so every menu destination has the same title style.
- It renders a bar with: left primary accent (4px), white/card background, padding, and title typography (`text-xl` / `sm:text-2xl`, bold, slate-900 / dark slate-100).
- **Usage:** `<PageTitleBar title={t("Page Title")} className="mb-4" />` or with actions: `<PageTitleBar title={t("Page Title")} actions={<Button>...</Button>} className="mb-4" />`.
- Do **not** put a bare `<h1>` or `<Typography variant="h4">` as the main page heading; use PageTitleBar for consistency.
2. **Content:** Fragments or divs with `space-y-4` (or `Stack spacing={2}` in MUI) between sections. No extra full-width background wrapper.
### Search criteria
- **When using the shared SearchBox component:** It already uses the standard card style. Ensure the parent page does not wrap it in another card.
- **When building a custom search/query bar:** Use the shared class so it matches SearchBox:
- Label for “Search criteria” style: `className="app-search-criteria-label"` if you need a small uppercase label.
- **Search button:** Primary action = blue (MUI `variant="contained"` `color="primary"`, or Tailwind `bg-blue-500 text-white`). Reset = outline with neutral border (e.g. MUI `variant="outlined"` with slate border, or Tailwind `border border-slate-300`).
### Forms & inputs
- **Standard look (enforced by MUI theme):** White background, border `#e2e8f0` (neutral 200), focus ring primary blue. Use MUI `TextField` / `FormControl` / date pickers as-is; the theme in `src/theme/devias-material-kit` already matches this.
- **Tailwind-only forms (e.g. /ps):** Use the same tokens: `border border-slate-300`, `bg-white`, `focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20`, `text-slate-900`, `placeholder-slate-400`.
### Buttons
- **Primary action:** Blue filled — MUI `variant="contained"` `color="primary"` or Tailwind `bg-blue-500 text-white hover:bg-blue-600`.
- **Secondary / cancel:** Outline, neutral — MUI `variant="outlined"` with border `#e2e8f0` / `#334155` text, or Tailwind `border border-slate-300 text-slate-700 hover:bg-slate-100`.
- **Accent (e.g. export, success):** Green — MUI `color="success"` or Tailwind `bg-emerald-500` / `text-emerald-600` for outline.
- **Spacing:** Use `gap-2` or `gap-4` between buttons; keep padding multiples of 4 (e.g. `px-4 py-2`).
### Tables & grids
- **Container:** Wrap tables/grids in a card-style container so they match across pages:
- **Data grid (MUI X DataGrid):** Use `StyledDataGrid` from `@/components/StyledDataGrid`. It applies header bg neutral[50], header text neutral[700], cell padding and borders to match the standard.
- **Table (MUI Table):** Use `SearchResults` when you have a paginated list; it uses `Paper variant="outlined"` and theme table styles (header bg, borders).
- **Header row:** Background `bg-slate-50` / `neutral[50]`, text `text-slate-700` / `neutral[700]`, font-weight 600, padding `px-4 py-3` or theme default.
- **Standard card:** 8px radius, 1px border (`var(--border)` or `neutral[200]`), white background (`var(--card)`), light shadow (`0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)`). MUI `Card` and `Paper` are themed to match.
- **Search-criteria card:** Use class `app-search-criteria` (left 4px primary border, same radius and shadow as above).
### Menu bar & sidebar
- **App bar (top):** White background, 1px bottom border (`palette.divider`), no heavy shadow (`elevation={0}`). Toolbar with consistent min-height and horizontal padding. Profile and title use `text.secondary` and font-weight 600.
- **Sidebar (navigation drawer):** Same as cards: white background, 1px right border, light shadow. Logo area with padding and bottom border; nav list with 4px/8px margins, 8px border-radius on items. **Selected item:** primary light background tint, primary text/icon, font-weight 600. **Hover:** neutral hover background. Use `ListItemButton` with `mx: 1`, `minWidth: 40` on icons. Child items slightly smaller font (0.875rem).
- **Profile dropdown:** Menu with 8px radius, 1px border (outlined Paper). Dense list, padding on header and items. Sign out as `MenuItem`.
- **Selection logic:** Nav item is selected when `pathname === item.path` or `pathname.startsWith(item.path + "/")`. Parent with children expands on click; leaf items navigate via Link.
- **Icons:** Use one icon per menu item that matches the action or section (e.g. Dashboard, LocalShipping for delivery, CalendarMonth for scheduling, Settings for settings). Prefer distinct MUI icons so items are easy to scan; avoid reusing the same icon for many items.
### Reference implementations
- **/ps** — Tailwind-only: query bar (`app-search-criteria`), buttons, table container, modals. Good reference for Tailwind patterns.
- **/do** — SearchBox + StyledDataGrid inside Paper; page title on layout. Good reference for MUI + layout.
- **/jo** — SearchBox + SearchResults (Paper-wrapped table); page title on layout. Same layout and search pattern as /do.
When adding a **new page**, reuse the same structure: rely on the main layout for background/padding, use one optional standard `<h1>`, then SearchBox (or `app-search-criteria` for custom bars), then Paper-wrapped grid/table or other content, with buttons and forms following the rules above.
### Checklist for new pages
- [ ] No extra full-page wrapper (background/padding come from main layout).
- [ ] Page title: use `<PageTitleBar title={...} />` (optional `actions`). Add `className="mb-4"` for spacing below.
- [ ] Search/filter: use `SearchBox` or a div with `className="app-search-criteria"` for the bar.
- [ ] Tables/grids: wrap in `Paper variant="outlined"` (MUI) or `rounded-lg border border-slate-200 bg-white shadow-sm` (Tailwind); use `StyledDataGrid` or `SearchResults` where applicable.
- [ ] Buttons: primary = blue contained, secondary = outlined neutral, accent = green for success/export.
- [ ] Spacing: multiples of 4px (`p-4`, `gap-2`, `mb-4`); responsive with `sm`/`md`/`lg`.
src/app/(main)/chart/_components/EXCEL_EXPORT_STANDARD.md파일 보기
@@ -0,0 +1,161 @@
# Excel export standard (FPSMS frontend)
This document defines how **client-side** `.xlsx` exports should look and behave. **Implementation:** `exportChartToXlsx.ts` and `exportMultiSheetToXlsx()` — use these helpers for new reports so styling stays consistent.
## Scope (important)
| Export path | Follows this `.md`? |
|-------------|---------------------|
| **Next.js** builds the file via `exportChartToXlsx` / `exportMultiSheetToXlsx` (e.g. **PO 入倉記錄** / `rep-014`) | **Yes** — rules are enforced in code. |
| **Backend** returns ready-made `.xlsx` or Excel bytes (JasperReports, Apache POI, etc.; most `print-*` report endpoints) | **No — not automatically.** That code does **not** use this TypeScript module. To match the same **look** (grey headers, number formats, alignment), implement equivalent styling in Java/Kotlin or Jasper templates. See the backend companion doc below. |
**Backend companion (visual parity):**
`FPSMS-backend/docs/EXCEL_EXPORT_STANDARD.md` — same *rules*, for POI/Jasper implementers.
---
## 1. Library
| Item | Value |
|------|--------|
| Package | **`xlsx-js-style`** (not the plain `xlsx` community build) |
| Reason | Plain SheetJS **does not persist** cell styles (`fill`, `alignment`, `numFmt`) in the written file. `xlsx-js-style` is a compatible fork that **does**. |
---
## 2. Data shape
- Rows are **`Record<string, unknown>[]`** (array of plain objects).
- **First object’s keys** become the **header row** (column titles). Every row should use the **same keys** in the same order for a rectangular sheet.
- Prefer **real JavaScript `number`** values for amounts where possible; the exporter will apply number formats. Strings that look like numbers (e.g. `"1,234.56"`) are parsed for money columns.
---
## 3. Processing order (per sheet)
After `json_to_sheet(rows)`:
1. **`!cols`** — column width heuristic (see §4).
2. **`applyHeaderRowStyle`** — header row styling (see §5).
3. **`applyMoneyColumnNumberFormats`** — money columns only, data rows (see §6).
4. **`applyNumericColumnRightAlign`** — money + quantity columns, **all rows including header** (see §7).
---
## 4. Column width (`!cols`)
- For each column: `wch = max(12, headerText.length + 4)`.
- Adjust if a report needs fixed widths; default keeps bilingual headers readable.
---
## 5. Header row style (row 0)
Applied to **every** header cell first; numeric columns get alignment overridden in step 4.
| Property | Value |
|----------|--------|
| Font | Bold, black `rgb: "000000"` |
| Fill | Solid, `fgColor: "D9D9D9"` (light grey) |
Most other reports on `/report` download Excel/PDF **generated on the server** (Jasper, etc.). Those **do not** run this TypeScript pipeline; use **`FPSMS-backend/docs/EXCEL_EXPORT_STANDARD.md`** if you want the same visual rules there.
---
## 12. Checklist for new Excel exports
1. Import from **`xlsx-js-style`** only if building sheets manually; otherwise call **`exportChartToXlsx`** or **`exportMultiSheetToXlsx`**.
2. Use **stable header strings** that match §6 / §7 if the column is amount or quantity.
3. Pass **numbers** for amounts when possible.
4. If you need a **new** category (e.g. “Rate”, “折扣”), extend the regex constants in `exportChartToXlsx.ts` and **update this document**.
5. Keep filenames and sheet names user-readable; remember the **31-character** sheet limit.
- Use data that “generates the GRN” (Goods Received Note): e.g. **stock-in lines** (or equivalent) linked to **PO** and **delivery note**.
- Filter by:
- `receiptDate` (or equivalent) between `receiptDateStart` and `receiptDateEnd` when provided.
- `itemCode` when provided.
- Return one row per GRN line with at least: **PO/delivery note no.**, **itemCode**, **itemName**, **qty** (e.g. `acceptedQty`), **uom**, and optionally receipt date, lot, expiry, supplier, status.
Frontend builds the Excel from this JSON. Columns include: PO No., Delivery Note No., Receipt Date, Item Code, Item Name, Qty, Demand Qty, UOM, Supplier Lot No. 供應商批次, Expiry Date, Supplier Code, Supplier, 入倉狀態, **GRN Code** (`m18_goods_receipt_note_log.grn_code`), **GRN Id** (`m18_record_id`).
## Frontend Excel styling (shared standard)
Header colours, number formats (`#,##0.00` for amounts), and column alignment are defined in: