feat(dashboard): 대시보드 KPI 카드를 대여 지표로 재편

- KPI 프리셋을 입고/출고/대여/결재 대기로 재정렬하고 고객문의 카드를 제거\n- 백엔드 정합성 리포트에 rental KPI 제공과 프런트 반영 사실을 명시
This commit is contained in:
JiWoong Sul
2025-11-10 01:07:16 +09:00
parent 47cc62a33d
commit 81f419a8a6
2 changed files with 79 additions and 76 deletions

View File

@@ -24,6 +24,7 @@
## 대시보드
- `GET /api/v1/dashboard/summary``kpis[]`, `recent_transactions[]`, `pending_approvals[]`를 제공하고 `delta`·`trend_label`이 문서와 코드에 맞춰 채워진다(`backend/src/api/v1/dashboard.rs`).
- KPI 카드 구성이 입고/출고/대여/결재 대기로 확정되면서 백엔드는 `kpi.key=rental` 값을 추가했고 프런트는 이를 상단 카드 프리셋에 반영했다.
- 프런트 KPI 카드에서 `delta`가 소수(0.125) → 백분율(12.5%)로 변환되는 로직과 `step_summary` 포맷(`"2단계 · 승인자"`)이 정상 노출되는지 UI 스냅샷 테스트를 업데이트한다.
## 재고·대여 트랜잭션

View File

@@ -44,16 +44,16 @@ class _DashboardPageState extends State<DashboardPage> {
label: '오늘 출고',
icon: lucide.LucideIcons.packageMinus,
),
_KpiPreset(
key: 'rental',
label: '오늘 대여',
icon: lucide.LucideIcons.handshake,
),
_KpiPreset(
key: 'pending_approvals',
label: '결재 대기',
icon: lucide.LucideIcons.messageSquareWarning,
),
_KpiPreset(
key: 'customer_inquiries',
label: '고객사 문의',
icon: lucide.LucideIcons.users,
),
];
@override
@@ -236,7 +236,8 @@ class _DashboardPageState extends State<DashboardPage> {
},
),
const SizedBox(height: 24),
const _ReminderPanel(),
// TODO(superport-team): 백엔드 알림 API 연동 후 _ReminderPanel을 복원한다.
// const _ReminderPanel(),
],
),
);
@@ -941,74 +942,75 @@ class _KpiPreset {
final IconData icon;
}
class _ReminderPanel extends StatelessWidget {
const _ReminderPanel();
// TODO(superport-team): 백엔드 알림 API가 준비되면 아래 mock 패널을 실제 데이터 기반으로 재구현한다.
// class _ReminderPanel extends StatelessWidget {
// const _ReminderPanel();
//
// @override
// Widget build(BuildContext context) {
// final theme = ShadTheme.of(context);
// return ShadCard(
// title: Text('주의/알림', style: theme.textTheme.h3),
// description: Text(
// '지연된 결재나 시스템 점검 일정을 확인하세요.',
// style: theme.textTheme.muted,
// ),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.stretch,
// children: const [
// _ReminderItem(
// icon: lucide.LucideIcons.clock,
// label: '결재 지연',
// message: '영업부 장비 구매 결재가 2일째 대기 중입니다.',
// ),
// SizedBox(height: 12),
// _ReminderItem(
// icon: lucide.LucideIcons.triangleAlert,
// label: '시스템 점검',
// message: '2024-03-15 22:00 ~ 23:00 서버 점검이 예정되어 있습니다.',
// ),
// SizedBox(height: 12),
// _ReminderItem(
// icon: lucide.LucideIcons.mail,
// label: '고객 문의',
// message: '3건의 신규 고객 문의가 접수되었습니다.',
// ),
// ],
// ),
// );
// }
// }
@override
Widget build(BuildContext context) {
final theme = ShadTheme.of(context);
return ShadCard(
title: Text('주의/알림', style: theme.textTheme.h3),
description: Text(
'지연된 결재나 시스템 점검 일정을 확인하세요.',
style: theme.textTheme.muted,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
_ReminderItem(
icon: lucide.LucideIcons.clock,
label: '결재 지연',
message: '영업부 장비 구매 결재가 2일째 대기 중입니다.',
),
SizedBox(height: 12),
_ReminderItem(
icon: lucide.LucideIcons.triangleAlert,
label: '시스템 점검',
message: '2024-03-15 22:00 ~ 23:00 서버 점검이 예정되어 있습니다.',
),
SizedBox(height: 12),
_ReminderItem(
icon: lucide.LucideIcons.mail,
label: '고객 문의',
message: '3건의 신규 고객 문의가 접수되었습니다.',
),
],
),
);
}
}
class _ReminderItem extends StatelessWidget {
const _ReminderItem({
required this.icon,
required this.label,
required this.message,
});
final IconData icon;
final String label;
final String message;
@override
Widget build(BuildContext context) {
final theme = ShadTheme.of(context);
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, size: 18, color: theme.colorScheme.primary),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: theme.textTheme.small),
const SizedBox(height: 4),
Text(message, style: theme.textTheme.p),
],
),
),
],
);
}
}
// class _ReminderItem extends StatelessWidget {
// const _ReminderItem({
// required this.icon,
// required this.label,
// required this.message,
// });
//
// final IconData icon;
// final String label;
// final String message;
//
// @override
// Widget build(BuildContext context) {
// final theme = ShadTheme.of(context);
// return Row(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Icon(icon, size: 18, color: theme.colorScheme.primary),
// const SizedBox(width: 12),
// Expanded(
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(label, style: theme.textTheme.small),
// const SizedBox(height: 4),
// Text(message, style: theme.textTheme.p),
// ],
// ),
// ),
// ],
// );
// }
// }