feat: add benefit history feature with lazy loading and pagination

- Implemented `getBenefitsHistory` method in `HomeRepository` to retrieve paginated benefit history.
- Enhanced `BenefitsOverviewCubit` to manage loading and displaying benefit history.
- Created `BenefitHistoryPage` for full-screen display of benefit history with infinite scroll support.
- Added `BenefitHistoryPreview` widget for expandable history preview in benefit cards.
- Introduced `BenefitHistoryRow` to display individual history records.
- Updated `BenefitsOverviewState` to include history management fields.
- Added new entities and use cases for handling benefit history.
- Created design system documentation for UI patterns and known gaps.
This commit is contained in:
Achintha Isuru
2026-03-18 17:21:30 -04:00
parent 1552f60e5b
commit 9039aa63d6
22 changed files with 1047 additions and 19 deletions

View File

@@ -0,0 +1,7 @@
# UI/UX Design Agent Memory
## Index
- [design-system-tokens.md](design-system-tokens.md) — Verified token values from actual source files
- [component-patterns.md](component-patterns.md) — Established component patterns in KROW staff app
- [design-gaps.md](design-gaps.md) — Known design system gaps and escalation items

View File

@@ -0,0 +1,87 @@
---
name: KROW Staff App Component Patterns
description: Established UI patterns, widget conventions, and design decisions confirmed in the KROW staff app codebase
type: project
---
## Card Pattern (standard surface)
Cards use:
- `UiColors.cardViewBackground` (white) background
- `Border.all(color: UiColors.border)` outline
- `BorderRadius.circular(UiConstants.radiusBase)` = 12dp
- `EdgeInsets.all(UiConstants.space4)` = 16dp padding
Do NOT use `UiColors.bgSecondary` as card background — that is for toggles/headers inside cards.
## Section Toggle / Expand-Collapse Header
Used for collapsible sections inside cards:
- Background: `UiColors.bgSecondary`
- Radius: `UiConstants.radiusMd` (6dp)
- Height: minimum 48dp (touch target)
- Label: `UiTypography.titleUppercase3m.textSecondary` for ALL-CAPS labels
- Trailing: `UiIcons.chevronDown` animated 180° via `AnimatedRotation`, 200ms
- Ripple: `InkWell` with `borderRadius: UiConstants.radiusMd` and splash `UiColors.primary.withValues(alpha: 0.06)`
## Shimmer Loading Pattern
Use `UiShimmer` wrapper + `UiShimmerLine` / `UiShimmerBox` / `UiShimmerCircle` primitives.
- Base color: `UiColors.muted`
- Highlight: `UiColors.background`
- For list content: 3 shimmer rows by default
- Do NOT use fixed height containers for shimmer — let content flow
## Status Badge (read-only, non-interactive)
Custom `Container` with pill shape:
- `borderRadius: UiConstants.radiusFull`
- `padding: EdgeInsets.symmetric(horizontal: space2, vertical: 2)`
- Label style: `UiTypography.footnote2b`
- Do NOT use the interactive `UiChip` widget for read-only display
Status color mapping:
- ACTIVE: bg=`tagActive`, fg=`textSuccess`
- PENDING: bg=`tagPending`, fg=`textWarning`
- INACTIVE/ENDED: bg=`tagFreeze`, fg=`textSecondary`
- ERROR: bg=`tagError`, fg=`textError`
## Inline Error Banner (inside card)
NOT a full-page error — a compact container inside the widget:
- bg: `UiColors.tagError`
- radius: `UiConstants.radiusMd`
- Icon: `UiIcons.error` at `iconMd` (20dp), color: `UiColors.destructive`
- Title: `body2m.textError`
- Retry link: `body3r.primary` with `TextDecoration.underline`
## Inline Empty State (inside card)
NOT `UiEmptyState` widget (that is full-page). Use compact inline version:
- `Icon(UiIcons.clock, size: iconXl=32, color: UiColors.iconDisabled)`
- `body2r.textSecondary` label
- `EdgeInsets.symmetric(vertical: space6)` padding
## AnimatedSize for Expand/Collapse
```dart
AnimatedSize(
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
child: isExpanded ? content : const SizedBox.shrink(),
)
```
## Benefits Feature Structure
Legacy benefits: `apps/mobile/legacy/legacy-staff-app/lib/features/profile/benefits/`
V2 domain entity: `apps/mobile/packages/domain/lib/src/entities/benefits/benefit.dart`
V2 history entity: needs creation at `packages/domain/lib/src/entities/benefits/benefit_history.dart`
Benefit history is lazy-loaded per card (not with the initial overview fetch).
History state is cached in BLoC as `Map<String, AsyncValue<List<BenefitHistory>>>` keyed by benefitId.
## Screen Page Pattern (overview pages)
Uses `CustomScrollView` with `SliverList` for header + `SliverPadding` wrapping `SliverList.separated` for content.
Bottom padding on content sliver: `EdgeInsets.fromLTRB(16, 16, 16, 120)` to clear bottom nav bar.

View File

@@ -0,0 +1,22 @@
---
name: KROW Design System Gaps and Escalations
description: Known missing tokens, open design questions, and items requiring escalation to PM or design system owner
type: project
---
## Open Escalations (as of 2026-03-18)
### 1. No Dark Theme Token Definitions
**Severity:** High
**Detail:** `ui_colors.dart` defines a single light `ColorScheme`. Tag colors (`tagActive`, `tagPending`, `tagFreeze`, `tagError`) have no dark mode equivalents. No dark theme has been configured in `UiTheme`.
**Action:** Escalate to design system owner before any dark mode work. Until resolved, do not attempt dark mode overrides in feature widgets.
### 2. V2 History API — trackedHours Sign Convention
**Severity:** Medium
**Detail:** `GET /staff/profile/benefits/history` returns `trackedHours` as a positive integer. There is no `transactionType` field to distinguish accruals from deductions (used hours). Design assumes accrual-only for now with `+` prefix in `UiColors.textSuccess`.
**Action:** Escalate to PM/backend. Recommend adding `transactionType: "ACCRUAL" | "USAGE"` or signed integer to distinguish visually.
### 3. Missing Localization Keys for Benefits History
**Severity:** Low (implementation blocker, not design blocker)
**Detail:** New keys under `benefits.history.*` need to be added to both `en.i18n.json` and `es.i18n.json` in `packages/core_localization/lib/src/l10n/`. Must be coordinated with Mobile Feature Agent who runs `dart run slang`.
**Action:** Hand off key list to Mobile Feature Agent.

View File

@@ -0,0 +1,102 @@
---
name: KROW Design System Token Reference
description: Verified token values from actual source files in apps/mobile/packages/design_system/lib/src/
type: reference
---
## Source Files (verified 2026-03-18)
- `ui_colors.dart` — all color tokens
- `ui_typography.dart` — all text styles (primary font: Instrument Sans, secondary: Space Grotesk)
- `ui_constants.dart` — spacing, radius, icon sizes
- `ui_icons.dart` — icon aliases over LucideIcons (primary) + FontAwesomeIcons (secondary)
## Key Color Tokens (hex values confirmed)
| Token | Hex | Use |
|-------|-----|-----|
| `UiColors.background` | `#FAFBFC` | Page background |
| `UiColors.cardViewBackground` | `#FFFFFF` | Card surface |
| `UiColors.bgSecondary` | `#F1F3F5` | Toggle/section headers |
| `UiColors.bgThird` | `#EDF0F2` | — |
| `UiColors.primary` | `#0A39DF` | Brand blue |
| `UiColors.textPrimary` | `#121826` | Main text |
| `UiColors.textSecondary` | `#6A7382` | Secondary/muted text |
| `UiColors.textInactive` | `#9CA3AF` | Disabled/placeholder |
| `UiColors.textSuccess` | `#0A8159` | Green text (darker than success icon) |
| `UiColors.textError` | `#F04444` | Red text |
| `UiColors.textWarning` | `#D97706` | Amber text |
| `UiColors.success` | `#10B981` | Green brand color |
| `UiColors.destructive` | `#F04444` | Red brand color |
| `UiColors.border` | `#D1D5DB` | Default border |
| `UiColors.separatorSecondary` | `#F1F5F9` | Light dividers |
| `UiColors.tagActive` | `#DCFCE7` | Active status badge bg |
| `UiColors.tagPending` | `#FEF3C7` | Pending badge bg |
| `UiColors.tagError` | `#FEE2E2` | Error banner bg |
| `UiColors.tagFreeze` | `#F3F4F6` | Ended/frozen badge bg |
| `UiColors.tagInProgress` | `#DBEAFE` | In-progress badge bg |
| `UiColors.iconDisabled` | `#D1D5DB` | Disabled icon color |
| `UiColors.muted` | `#F1F3F5` | Shimmer base color |
## Key Spacing Constants
| Token | Value |
|-------|-------|
| `space1` | 4dp |
| `space2` | 8dp |
| `space3` | 12dp |
| `space4` | 16dp |
| `space5` | 20dp |
| `space6` | 24dp |
| `space8` | 32dp |
| `space10` | 40dp |
| `space12` | 48dp |
## Key Radius Constants
| Token | Value |
|-------|-------|
| `radiusSm` | 4dp |
| `radiusMd` (radiusMdValue) | 6dp |
| `radiusBase` | 12dp |
| `radiusLg` | 12dp (BorderRadius.circular(12)) |
| `radiusXl` | 16dp |
| `radius2xl` | 24dp |
| `radiusFull` | 999dp |
NOTE: `radiusBase` is a `double` (12.0), `radiusLg` is a `BorderRadius`. Use `BorderRadius.circular(UiConstants.radiusBase)` when a double is needed.
## Icon Sizes
| Token | Value |
|-------|-------|
| `iconXs` | 12dp |
| `iconSm` | 16dp |
| `iconMd` | 20dp |
| `iconLg` | 24dp |
| `iconXl` | 32dp |
## Key Typography Styles (Instrument Sans)
| Token | Size | Weight | Notes |
|-------|------|--------|-------|
| `display1b` | 26px | 600 | letterSpacing: -1 |
| `title1b` | 18px | 600 | height: 1.5 |
| `title1m` | 18px | 500 | height: 1.5 |
| `title2b` | 16px | 600 | height: 1.1 |
| `body1m` | 16px | 600 | letterSpacing: -0.025 |
| `body1r` | 16px | 400 | letterSpacing: -0.05 |
| `body2b` | 14px | 700 | height: 1.5 |
| `body2m` | 14px | 500 | height: 1.5 |
| `body2r` | 14px | 400 | letterSpacing: 0.1 |
| `body3r` | 12px | 400 | height: 1.5 |
| `body3m` | 12px | 500 | letterSpacing: -0.1 |
| `footnote1r` | 12px | 400 | letterSpacing: 0.05 |
| `footnote1m` | 12px | 500 | — |
| `footnote2b` | 10px | 700 | — |
| `footnote2r` | 10px | 400 | — |
| `titleUppercase3m` | 12px | 500 | letterSpacing: 0.7 — use for ALL-CAPS section labels |
## Typography Color Extension
`UiTypography` styles have a `.textSecondary`, `.textSuccess`, `.textError`, `.textWarning`, `.primary`, `.white` extension defined in `TypographyColors`. Use these instead of `.copyWith(color: ...)` where possible for brevity.