전역 구조 리팩터링 및 테스트 확장

This commit is contained in:
JiWoong Sul
2025-09-29 01:51:47 +09:00
parent c00c0c9ab2
commit fef7108479
70 changed files with 7709 additions and 3185 deletions

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:intl/intl.dart' as intl;
import 'package:lucide_icons_flutter/lucide_icons.dart' as lucide;
import 'package:shadcn_ui/shadcn_ui.dart';
@@ -7,6 +8,8 @@ import '../../../../../core/config/environment.dart';
import '../../../../../core/constants/app_sections.dart';
import '../../../../../widgets/app_layout.dart';
import '../../../../../widgets/components/filter_bar.dart';
import '../../../../../widgets/components/superport_date_picker.dart';
import '../../../../../widgets/components/superport_table.dart';
import '../../../../../widgets/spec_page.dart';
import '../../domain/entities/approval_history_record.dart';
import '../../domain/repositories/approval_history_repository.dart';
@@ -145,6 +148,19 @@ class _ApprovalHistoryEnabledPageState
),
],
toolbar: FilterBar(
actions: [
ShadButton.outline(
onPressed: _controller.isLoading ? null : _applyFilters,
child: const Text('검색 적용'),
),
ShadButton.ghost(
onPressed:
_controller.isLoading || !_controller.hasActiveFilters
? null
: _resetFilters,
child: const Text('필터 초기화'),
),
],
children: [
SizedBox(
width: 240,
@@ -180,21 +196,24 @@ class _ApprovalHistoryEnabledPageState
),
SizedBox(
width: 220,
child: ShadButton.outline(
onPressed: _pickDateRange,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
const Icon(lucide.LucideIcons.calendar, size: 16),
const SizedBox(width: 8),
Text(
_dateRange == null
? '기간 선택'
: '${_formatDate(_dateRange!.start)} ~ ${_formatDate(_dateRange!.end)}',
child: SuperportDateRangePickerButton(
value: _dateRange,
dateFormat: intl.DateFormat('yyyy-MM-dd'),
enabled: !_controller.isLoading,
firstDate: DateTime(DateTime.now().year - 5),
lastDate: DateTime(DateTime.now().year + 1),
initialDateRange:
_dateRange ??
DateTimeRange(
start: DateTime.now().subtract(const Duration(days: 7)),
end: DateTime.now(),
),
],
),
onChanged: (range) {
if (range == null) return;
setState(() => _dateRange = range);
_controller.updateDateRange(range.start, range.end);
_controller.fetch(page: 1);
},
),
),
if (_dateRange != null)
@@ -202,17 +221,6 @@ class _ApprovalHistoryEnabledPageState
onPressed: _controller.isLoading ? null : _clearDateRange,
child: const Text('기간 초기화'),
),
ShadButton.outline(
onPressed: _controller.isLoading ? null : _applyFilters,
child: const Text('검색 적용'),
),
ShadButton.ghost(
onPressed:
_controller.isLoading || !_controller.hasActiveFilters
? null
: _resetFilters,
child: const Text('필터 초기화'),
),
],
),
child: ShadCard(
@@ -283,27 +291,6 @@ class _ApprovalHistoryEnabledPageState
_controller.fetch(page: 1);
}
Future<void> _pickDateRange() async {
final now = DateTime.now();
final initial =
_dateRange ??
DateTimeRange(
start: DateTime(now.year, now.month, now.day - 7),
end: now,
);
final range = await showDateRangePicker(
context: context,
initialDateRange: initial,
firstDate: DateTime(now.year - 5),
lastDate: DateTime(now.year + 1),
);
if (range != null) {
setState(() => _dateRange = range);
_controller.updateDateRange(range.start, range.end);
_controller.fetch(page: 1);
}
}
void _clearDateRange() {
setState(() => _dateRange = null);
_controller.updateDateRange(null, null);
@@ -318,10 +305,6 @@ class _ApprovalHistoryEnabledPageState
_controller.fetch(page: 1);
}
String _formatDate(DateTime date) {
return DateFormat('yyyy-MM-dd').format(date.toLocal());
}
String _actionLabel(ApprovalHistoryActionFilter filter) {
switch (filter) {
case ApprovalHistoryActionFilter.all:
@@ -349,58 +332,60 @@ class _ApprovalHistoryTable extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = ShadTheme.of(context);
final normalizedQuery = query.trim().toLowerCase();
final header = [
'ID',
'결재번호',
'단계순서',
'승인자',
'행위',
'변경전 상태',
'변경 상태',
'작업일시',
'비고',
].map((label) => ShadTableCell.header(child: Text(label))).toList();
final columns = const [
Text('ID'),
Text('결재번호'),
Text('단계순서'),
Text('승인자'),
Text('행위'),
Text('변경 상태'),
Text('변경후 상태'),
Text('작업일시'),
Text('비고'),
];
final rows = histories.map((history) {
final isHighlighted =
normalizedQuery.isNotEmpty &&
history.approvalNo.toLowerCase().contains(normalizedQuery);
return [
ShadTableCell(child: Text(history.id.toString())),
ShadTableCell(
child: Text(
history.approvalNo,
style: isHighlighted
? ShadTheme.of(
context,
).textTheme.small.copyWith(fontWeight: FontWeight.w600)
: null,
),
),
ShadTableCell(
child: Text(
history.stepOrder == null ? '-' : history.stepOrder.toString(),
),
),
ShadTableCell(child: Text(history.approver.name)),
ShadTableCell(child: Text(history.action.name)),
ShadTableCell(child: Text(history.fromStatus?.name ?? '-')),
ShadTableCell(child: Text(history.toStatus.name)),
ShadTableCell(
child: Text(dateFormat.format(history.actionAt.toLocal())),
),
ShadTableCell(
child: Text(
history.note?.trim().isEmpty ?? true ? '-' : history.note!,
),
final highlightStyle = theme.textTheme.small.copyWith(
fontWeight: FontWeight.w600,
color: theme.colorScheme.foreground,
);
final noteText = history.note?.trim();
final noteContent = noteText?.isNotEmpty == true ? noteText : null;
final subLabelStyle = theme.textTheme.muted.copyWith(
fontSize: (theme.textTheme.muted.fontSize ?? 14) - 1,
);
return <Widget>[
Text(history.id.toString()),
Text(history.approvalNo, style: isHighlighted ? highlightStyle : null),
Text(history.stepOrder == null ? '-' : history.stepOrder.toString()),
Text(history.approver.name),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(history.action.name),
if (noteContent != null) Text(noteContent, style: subLabelStyle),
],
),
Text(history.fromStatus?.name ?? '-'),
Text(history.toStatus.name),
Text(dateFormat.format(history.actionAt.toLocal())),
Text(noteContent ?? '-'),
];
}).toList();
return ShadTable.list(
header: header,
children: rows,
return SuperportTable(
columns: columns,
rows: rows,
rowHeight: 64,
maxHeight: 520,
columnSpanExtent: (index) {
switch (index) {
case 1: