feat(dashboard): 대시보드 KPI 카드를 대여 지표로 재편
- KPI 프리셋을 입고/출고/대여/결재 대기로 재정렬하고 고객문의 카드를 제거\n- 백엔드 정합성 리포트에 rental KPI 제공과 프런트 반영 사실을 명시
This commit is contained in:
@@ -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 스냅샷 테스트를 업데이트한다.
|
||||
|
||||
## 재고·대여 트랜잭션
|
||||
|
||||
@@ -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),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
Reference in New Issue
Block a user