Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4be163e
refactor(frontend): introduce UserAvatar with real photo support
ilramdhan Jul 3, 2026
6ac8619
feat(frontend): merge Classification + Feasibility into one sequentia…
ilramdhan Jul 3, 2026
1c7cbc3
feat(frontend): fill tracking card — show product names + assignee id…
ilramdhan Jul 3, 2026
17e25d2
feat(frontend): approval param visibility — form fields + FillApprova…
ilramdhan Jul 3, 2026
9fc9e8e
fix(frontend): correct cache invalidation on route link/unlink + lock…
ilramdhan Jul 3, 2026
c69308c
docs(frontend): update CLAUDE.md §6.7 for UserAvatar + fix test refs
ilramdhan Jul 3, 2026
98f6390
fix(frontend): routing panel lock guard UI + fill-tasks page cleanup
ilramdhan Jul 3, 2026
09d2ff0
fix(frontend): skip classification step when feasibility is NOT_FEASIBLE
ilramdhan Jul 3, 2026
bbee280
feat(frontend): param summary card — show product name + Level N inst…
ilramdhan Jul 3, 2026
13937c5
fix(frontend): remove redundant X close button from single-level drawers
ilramdhan Jul 3, 2026
1e3d544
feat(frontend): sort param values by group + order in all fill drawers
ilramdhan Jul 3, 2026
8a95e06
fix(frontend): remove Cancel button — keep Close and Reject only
ilramdhan Jul 3, 2026
8291e9c
fix(frontend): move feasibility section above classification in revie…
ilramdhan Jul 3, 2026
6888665
feat(frontend): fill tracking card — show product name below product …
ilramdhan Jul 3, 2026
8d91da1
fix(frontend): remove redundant X close button from param detail drawers
ilramdhan Jul 3, 2026
10a46e3
feat(finance): group param values by display_group in param-detail-dr…
ilramdhan Jul 3, 2026
af32915
feat(routes): polish list + detail pages — sort/filter/dark-mode/UX f…
ilramdhan Jul 3, 2026
c52308a
feat(finance): routing pages UX — list polish + react-flow uid keying…
ilramdhan Jul 8, 2026
4289d17
fix(finance): product master row navigation via anchor overlay instea…
ilramdhan Jul 8, 2026
910061c
docs: expand CLAUDE.md with key dependencies + improvement notes from…
ilramdhan Jul 8, 2026
22e7958
feat(finance): product request classification + workflow revamp (fron…
ilramdhan Jul 8, 2026
c8db9b5
test(finance): rewrite CPR E2E spec for RoutingResolver/SubmitAndDeci…
ilramdhan Jul 8, 2026
a8e339d
fix(finance): simplify product request form and detail panel
ilramdhan Jul 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 114 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,43 @@ npx tsc --noEmit # Type-check only — run after every significant chang
npx shadcn@latest add [name] # Add shadcn/ui component
```

### Key Dependencies

| Package | Version | Purpose |
|---------|---------|---------|
| next | 16.x | Framework (App Router) |
| react | 19.x | UI library |
| @tanstack/react-query | 5.x | Server state |
| zustand | 5.x | Client state |
| react-hook-form | 7.x | Forms |
| zod | 3.x | Validation |
| @grpc/grpc-js | 1.x | gRPC client (BFF) |
| tailwindcss | 4.x | Styling |
| sonner | — | Toast notifications |
| lucide-react | — | Icons |
| recharts | — | Charts |

### Testing Stack

- Vitest + Testing Library + MSW (Mock Service Worker)
- Setup file: `src/__tests__/setup.ts` — auto-cleanup, MSW server, `matchMedia`/`ResizeObserver` mocks
- Test files: `src/__tests__/**/*.test.{ts,tsx}`
- Current coverage: query-key unit tests + component filter tests (see §Improvement Notes for gaps)

### Environment Variables (`.env.local`)

```bash
# gRPC endpoints (server-side only, NOT exposed to the browser)
IAM_GRPC_HOST=localhost
IAM_GRPC_PORT=50052
FINANCE_GRPC_HOST=localhost
FINANCE_GRPC_PORT=50051

# Public variables (exposed to the browser)
NEXT_PUBLIC_API_URL= # Leave empty for relative paths (recommended)
NEXT_PUBLIC_APP_URL=http://localhost:3000
```

---

## 2. Architecture Overview
Expand All @@ -96,10 +133,31 @@ The frontend **never** calls the Go backend directly. All requests go through `/
QueryProvider → ThemeProvider → AuthProvider → PermissionProvider → {children}
```

- `AuthProvider` — user state, login/logout, auto token refresh every 10 min
- `AuthProvider` — user state, login/logout, auto token refresh every 10 min, **plus a silent refresh on mount** (attempts a token refresh once when the app loads, ahead of the 10-min interval)
- `PermissionProvider` — `hasPermission(code)`, `hasAnyRole(...roles)`
- `QueryProvider` — TanStack Query client (staleTime 60s, refetchOnWindowFocus false)

### Route Protection (`src/proxy.ts`)

Next.js 16 route protection (replaces the old `middleware.ts`):

- **Public routes**: `/login`, `/forgot-password`, `/reset-password`, `/verify-otp`
- **Protected routes**: everything else — redirect to `/login?callbackUrl=...` when unauthenticated
- **API routes**: pass through untouched — each BFF route handles its own auth
- Auth check is based on the presence of `access_token` + `refresh_token` cookies

### Dynamic Sidebar System

The sidebar is **fully dynamic** — driven by database menu data, not a hardcoded nav config:

1. Backend `GetMenuTree` RPC returns hierarchical menu data already filtered by the current user's permissions
2. BFF route `/api/v1/iam/menus/tree` proxies the RPC
3. `useMenuTree()` hook (`src/hooks/iam/use-menu.ts`) fetches the tree → `menuTreeToNavGroups()` converts it to sidebar nav-group shape
4. Icons lazy-load via `preloadMenuIcons()` + `resolveIcon()` in `src/types/iam/menu.ts` (dynamic imports from `lucide-react`)
5. `AppSidebar` renders the resulting nav groups

**Menu permission visibility rule**: a menu with **no** rows in `menu_permissions` is visible to **all** authenticated users; a menu **with** rows requires the user to hold at least one matching permission. Parent menus filtered out by permission drop their children too — `buildMenuTree()` only keeps children under a parent that itself survived the filter.

---

## 3. Directory Structure
Expand Down Expand Up @@ -502,19 +560,20 @@ Centered dashed-border box. Use whenever a list or section has no data.
- Resolves UUID → full name via `useUser()` hook (TanStack Query, cached)
- Never display raw UUIDs to users

### 6.7 UserInitials (avatar)
### 6.7 UserAvatar (photo or initials)

**File**: `src/components/finance/cost-request-comment/comments-panel.tsx` (exported)
**File**: `src/components/common/user-avatar.tsx`

```tsx
import { UserInitials } from "@/components/finance/cost-request-comment/comments-panel"
import { UserAvatar } from "@/components/common/user-avatar"

<UserInitials userId={comment.authorUserId} className="mt-0.5 shrink-0" />
<UserAvatar userId={comment.authorUserId} colorHash className="mt-0.5 shrink-0" />
```

- Resolves full name via `useUser()` → derives initials ("Ilham Ramadhan" → "IR")
- Deterministic color per userId (hash-based from 6-color palette)
- Resolves full name + `profilePictureUrl` via `useUser()` → renders the real photo (`AvatarImage`) when set, else falls back to initials ("Ilham Ramadhan" → "IR")
- `colorHash` prop enables the deterministic color-per-userId fallback (hash-based from 6-color palette); omit it to use the plain shadcn muted fallback (used by `nav-user.tsx`/`profile-header.tsx`)
- Falls back to first char of userId if name not yet resolved
- Supersedes the former `UserInitials` component (removed 2026-07-03) — same hook, same deterministic-color logic, now also renders a real photo when available

### 6.8 ScrollableDialog (tall forms)

Expand Down Expand Up @@ -946,6 +1005,20 @@ export const ACTIVE_FILTER_OPTIONS = [

**Never** edit files in `src/types/generated/` — they are auto-generated from proto.

### Response normalization (camelCase + snake_case)

Backend responses aren't guaranteed to arrive in one casing consistently, so normalizer functions read both:

```ts
function normalizeUOM(raw: RawUOM): NormalizedUOM {
return {
uomId: raw.uomId || raw.uom_id || "",
uomCode: raw.uomCode || raw.uom_code || "",
// ...
}
}
```

---

## 12. BFF API Route Pattern
Expand Down Expand Up @@ -1042,6 +1115,23 @@ getCompanyClient() // IAM Organization

All clients are singletons via `globalThis` (survive HMR in dev).

### gRPC → HTTP error mapping

**File**: `src/lib/grpc/errors.ts`

```ts
const GRPC_TO_HTTP = {
[grpc.status.NOT_FOUND]: 404,
[grpc.status.ALREADY_EXISTS]: 409,
[grpc.status.INVALID_ARGUMENT]: 400,
[grpc.status.PERMISSION_DENIED]: 403,
[grpc.status.UNAUTHENTICATED]: 401,
// ...
}
```

`isGrpcError()` + `handleGrpcError()` use this table to translate a caught gRPC error into the right `NextResponse` status before returning the standard `{ base, data }` envelope.

---

## 13. Loading & Skeleton Pattern
Expand Down Expand Up @@ -1147,11 +1237,11 @@ const STATUS_OPTIONS = [
|----------|---------------------|
| Display full name | `<UserName userId={id} />` |
| Display full name (compact, no @username) | `<UserName userId={id} compact />` |
| Avatar circle with initials | `<UserInitials userId={id} />` |
| Avatar circle with photo or initials | `<UserAvatar userId={id} colorHash />` |
| Department / org unit name | `<DeptName deptCode={code} />` |

- `UserName` and `UserInitials` both call `useUser(userId)` internally — TanStack Query caches the result, so the same user appearing 10× in a list triggers only one network request
- `UserInitials` derives initials from full name word boundaries: "Ilham Ramadhan" → "IR", single word → "I"
- `UserName` and `UserAvatar` both call `useUser(userId)` internally — TanStack Query caches the result, so the same user appearing 10× in a list triggers only one network request
- `UserAvatar` derives initials from full name word boundaries: "Ilham Ramadhan" → "IR", single word → "I"; renders the real photo instead when `profilePictureUrl` is set
- Colors are deterministic per userId (hash mod 6 palette) — same user always gets same color

---
Expand Down Expand Up @@ -1217,3 +1307,17 @@ When building a new CRUD feature from scratch, complete in this order:
9. `src/app/(dashboard)/{module}/{resource}/page.tsx` — server component
10. `src/app/(dashboard)/{module}/{resource}/loading.tsx` — skeleton
11. `src/app/(dashboard)/{module}/{resource}/{resource}-page-client.tsx` — client component wiring everything together

---

## 18. Improvement Notes

Known gaps identified during a 2026-07 documentation audit — not blockers, but worth knowing before assuming full coverage:

| Area | Severity | Note |
|------|----------|------|
| Limited test coverage | Medium | Only 2 test files exist (query keys + component filter) |
| No E2E tests | Medium | No Playwright/Cypress — only unit/component tests |
| Generated proto types in git | Low | Could be generated in CI instead of committed |
| No error boundary components | Low | Missing React error boundaries |
| No i18n/internationalization | Low | All strings hardcoded |
Loading
Loading