Files
superport_v2/lib/widgets/components/feedback.dart
JiWoong Sul d76f765814 feat(approvals): Approval Flow v2 프런트엔드 전면 개편
- 환경/라우터 모듈에 approval_flow_v2 토글을 추가하고 FeatureFlags 초기화를 연결 (.env*, lib/core/**)
- ApiClient 빌더·ApiRoutes 확장과 ApprovalRepositoryRemote 리팩터링으로 include·액션 시그니처를 정합화
- ApprovalFlow·ApprovalDraft 엔티티/레포/유즈케이스를 도입해 서버 초안과 단계 액션(승인·회수·재상신)을 지원
- Approval 컨트롤러·히스토리·템플릿 페이지와 공유 위젯을 재작성해 감사 로그·회수 UX·템플릿 CRUD를 반영
- Inbound/Outbound/Rental 컨트롤러·페이지에 결재 섹션을 삽입하고 대시보드 pending 카드 요약을 갱신
- SuperportDialog·FormField 등 공통 위젯을 보강하고 승인 위젯 가이드를 추가해 UI 가이드를 정리
- 결재/재고 테스트 픽스처와 단위·위젯·통합 테스트를 확장하고 flutter_test_config로 스테이징 호스트를 허용
- Approval Flow 레포트/플랜 문서를 업데이트하고 ApprovalFlow_System_Integration_and_ChangePlan.md를 추가
- 실행: flutter analyze, flutter test
2025-10-31 01:05:39 +09:00

147 lines
3.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
/// Superport 전역에서 사용하는 토스트/스낵바 헬퍼.
class SuperportToast {
SuperportToast._();
/// 성공 처리 완료를 사용자에게 안내한다.
static void success(BuildContext context, String message) {
_show(context, message, _ToastVariant.success);
}
/// 정보성 피드백을 노출한다.
static void info(BuildContext context, String message) {
_show(context, message, _ToastVariant.info);
}
/// 주의가 필요한 상황을 경고한다.
static void warning(BuildContext context, String message) {
_show(context, message, _ToastVariant.warning);
}
/// 오류 발생 시 스낵바를 표시한다.
static void error(BuildContext context, String message) {
_show(context, message, _ToastVariant.error);
}
/// 공통 스낵바 렌더링 로직.
static void _show(
BuildContext context,
String message,
_ToastVariant variant,
) {
final theme = ShadTheme.of(context);
final (Color background, Color foreground) = switch (variant) {
_ToastVariant.success => (
theme.colorScheme.primary,
theme.colorScheme.primaryForeground,
),
_ToastVariant.info => (
theme.colorScheme.accent,
theme.colorScheme.accentForeground,
),
_ToastVariant.warning => (
theme.colorScheme.secondary,
theme.colorScheme.secondaryForeground,
),
_ToastVariant.error => (
theme.colorScheme.destructive,
theme.colorScheme.destructiveForeground,
),
};
final messenger = ScaffoldMessenger.maybeOf(context);
if (messenger == null) {
return;
}
messenger
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content: Text(
message,
style: theme.textTheme.small.copyWith(
color: foreground,
fontWeight: FontWeight.w600,
),
),
backgroundColor: background,
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 3),
),
);
}
}
enum _ToastVariant { success, info, warning, error }
/// 기본 골격을 표현하는 스켈레톤 블록.
class SuperportSkeleton extends StatelessWidget {
const SuperportSkeleton({
super.key,
this.width,
this.height = 16,
this.borderRadius = const BorderRadius.all(Radius.circular(8)),
});
final double? width;
final double height;
final BorderRadius borderRadius;
@override
Widget build(BuildContext context) {
final theme = ShadTheme.of(context);
return AnimatedContainer(
duration: const Duration(milliseconds: 600),
decoration: BoxDecoration(
color: theme.colorScheme.muted,
borderRadius: borderRadius,
),
width: width,
height: height,
);
}
}
/// 리스트 데이터를 대체하는 반복 스켈레톤 레이아웃.
class SuperportSkeletonList extends StatelessWidget {
const SuperportSkeletonList({
super.key,
this.itemCount = 6,
this.height = 56,
this.gap = 12,
this.padding = const EdgeInsets.all(16),
});
/// 렌더링할 스켈레톤 행 개수.
final int itemCount;
/// 각 항목 높이.
final double height;
/// 행 사이 간격.
final double gap;
/// 전체 패딩.
final EdgeInsetsGeometry padding;
@override
Widget build(BuildContext context) {
return Padding(
padding: padding,
child: Column(
children: [
for (var i = 0; i < itemCount; i++) ...[
SuperportSkeleton(
height: height,
borderRadius: BorderRadius.circular(10),
),
if (i != itemCount - 1) SizedBox(height: gap),
],
],
),
);
}
}