전역 구조 리팩터링 및 테스트 확장
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user