결재 비활성 안내 개선 및 테이블 기능 보강
This commit is contained in:
@@ -19,6 +19,7 @@ class ApprovalHistoryController extends ChangeNotifier {
|
||||
DateTime? _from;
|
||||
DateTime? _to;
|
||||
String? _errorMessage;
|
||||
int _pageSize = 20;
|
||||
|
||||
PaginatedResult<ApprovalHistoryRecord>? get result => _result;
|
||||
bool get isLoading => _isLoading;
|
||||
@@ -27,6 +28,7 @@ class ApprovalHistoryController extends ChangeNotifier {
|
||||
DateTime? get from => _from;
|
||||
DateTime? get to => _to;
|
||||
String? get errorMessage => _errorMessage;
|
||||
int get pageSize => _result?.pageSize ?? _pageSize;
|
||||
|
||||
Future<void> fetch({int page = 1}) async {
|
||||
_isLoading = true;
|
||||
@@ -42,13 +44,14 @@ class ApprovalHistoryController extends ChangeNotifier {
|
||||
|
||||
final response = await _repository.list(
|
||||
page: page,
|
||||
pageSize: _result?.pageSize ?? 20,
|
||||
pageSize: _pageSize,
|
||||
query: _query.trim().isEmpty ? null : _query.trim(),
|
||||
action: action,
|
||||
from: _from,
|
||||
to: _to,
|
||||
);
|
||||
_result = response;
|
||||
_pageSize = response.pageSize;
|
||||
} catch (e) {
|
||||
_errorMessage = e.toString();
|
||||
} finally {
|
||||
@@ -86,6 +89,14 @@ class ApprovalHistoryController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void updatePageSize(int value) {
|
||||
if (value <= 0) {
|
||||
return;
|
||||
}
|
||||
_pageSize = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool get hasActiveFilters =>
|
||||
_query.trim().isNotEmpty ||
|
||||
_actionFilter != ApprovalHistoryActionFilter.all ||
|
||||
|
||||
@@ -10,7 +10,7 @@ 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 '../../../../../widgets/components/feature_disabled_placeholder.dart';
|
||||
import '../../domain/entities/approval_history_record.dart';
|
||||
import '../../domain/repositories/approval_history_repository.dart';
|
||||
import '../controllers/approval_history_controller.dart';
|
||||
@@ -22,41 +22,28 @@ class ApprovalHistoryPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final enabled = Environment.flag('FEATURE_APPROVALS_ENABLED');
|
||||
if (!enabled) {
|
||||
return const SpecPage(
|
||||
return AppLayout(
|
||||
title: '결재 이력 조회',
|
||||
summary: '결재 단계별 변경 이력을 조회합니다.',
|
||||
sections: [
|
||||
SpecSection(
|
||||
title: '조회 테이블',
|
||||
description: '수정 없이 이력 리스트만 제공.',
|
||||
table: SpecTable(
|
||||
columns: [
|
||||
'번호',
|
||||
'결재ID',
|
||||
'단계순서',
|
||||
'승인자',
|
||||
'행위',
|
||||
'변경전상태',
|
||||
'변경후상태',
|
||||
'작업일시',
|
||||
'비고',
|
||||
],
|
||||
rows: [
|
||||
[
|
||||
'1',
|
||||
'APP-20240301-001',
|
||||
'1',
|
||||
'최관리',
|
||||
'승인',
|
||||
'승인대기',
|
||||
'승인완료',
|
||||
'2024-03-01 10:30',
|
||||
'-',
|
||||
],
|
||||
],
|
||||
subtitle: '결재 단계별 변경 내역을 기간·행위·결재번호 기준으로 확인합니다.',
|
||||
breadcrumbs: const [
|
||||
AppBreadcrumbItem(label: '대시보드', path: dashboardRoutePath),
|
||||
AppBreadcrumbItem(label: '결재', path: '/approvals/history'),
|
||||
AppBreadcrumbItem(label: '결재 이력'),
|
||||
],
|
||||
actions: [
|
||||
Tooltip(
|
||||
message: '다운로드는 API 연동 이후 제공됩니다.',
|
||||
child: ShadButton(
|
||||
onPressed: null,
|
||||
leading: const Icon(lucide.LucideIcons.download, size: 16),
|
||||
child: const Text('엑셀 다운로드'),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: const FeatureDisabledPlaceholder(
|
||||
title: '결재 이력 기능 준비 중',
|
||||
description: '결재 이력 API가 공개되면 검색과 엑셀 다운로드 기능이 활성화됩니다.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -80,6 +67,10 @@ class _ApprovalHistoryEnabledPageState
|
||||
final DateFormat _dateTimeFormat = DateFormat('yyyy-MM-dd HH:mm');
|
||||
DateTimeRange? _dateRange;
|
||||
String? _lastError;
|
||||
static const _pageSizeOptions = [10, 20, 50];
|
||||
int? _sortColumnIndex;
|
||||
bool _sortAscending = true;
|
||||
static const _sortableColumns = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -125,9 +116,7 @@ class _ApprovalHistoryEnabledPageState
|
||||
final totalPages = result == null || result.pageSize == 0
|
||||
? 1
|
||||
: (result.total / result.pageSize).ceil().clamp(1, 9999);
|
||||
final hasNext = result == null
|
||||
? false
|
||||
: (result.page * result.pageSize) < result.total;
|
||||
final sortedHistories = _applySorting(histories);
|
||||
|
||||
return AppLayout(
|
||||
title: '결재 이력 조회',
|
||||
@@ -231,34 +220,6 @@ class _ApprovalHistoryEnabledPageState
|
||||
Text('$totalCount건', style: theme.textTheme.muted),
|
||||
],
|
||||
),
|
||||
footer: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'페이지 $currentPage / $totalPages',
|
||||
style: theme.textTheme.small,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
ShadButton.outline(
|
||||
size: ShadButtonSize.sm,
|
||||
onPressed: _controller.isLoading || currentPage <= 1
|
||||
? null
|
||||
: () => _controller.fetch(page: currentPage - 1),
|
||||
child: const Text('이전'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ShadButton.outline(
|
||||
size: ShadButtonSize.sm,
|
||||
onPressed: _controller.isLoading || !hasNext
|
||||
? null
|
||||
: () => _controller.fetch(page: currentPage + 1),
|
||||
child: const Text('다음'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
child: _controller.isLoading
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(48),
|
||||
@@ -273,9 +234,30 @@ class _ApprovalHistoryEnabledPageState
|
||||
),
|
||||
)
|
||||
: _ApprovalHistoryTable(
|
||||
histories: histories,
|
||||
histories: sortedHistories,
|
||||
dateFormat: _dateTimeFormat,
|
||||
query: _controller.query,
|
||||
pagination: SuperportTablePagination(
|
||||
currentPage: currentPage,
|
||||
totalPages: totalPages,
|
||||
totalItems: totalCount,
|
||||
pageSize: _controller.pageSize,
|
||||
pageSizeOptions: _pageSizeOptions,
|
||||
),
|
||||
onPageChange: (page) => _controller.fetch(page: page),
|
||||
onPageSizeChange: (size) {
|
||||
_controller.updatePageSize(size);
|
||||
_controller.fetch(page: 1);
|
||||
},
|
||||
isLoading: _controller.isLoading,
|
||||
sortableColumns: _sortableColumns,
|
||||
sortState: _sortColumnIndex == null
|
||||
? null
|
||||
: SuperportTableSortState(
|
||||
columnIndex: _sortColumnIndex!,
|
||||
ascending: _sortAscending,
|
||||
),
|
||||
onSortChanged: _handleSortChange,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -305,6 +287,58 @@ class _ApprovalHistoryEnabledPageState
|
||||
_controller.fetch(page: 1);
|
||||
}
|
||||
|
||||
void _handleSortChange(int columnIndex, bool ascending) {
|
||||
setState(() {
|
||||
_sortColumnIndex = columnIndex;
|
||||
_sortAscending = ascending;
|
||||
});
|
||||
}
|
||||
|
||||
List<ApprovalHistoryRecord> _applySorting(List<ApprovalHistoryRecord> items) {
|
||||
final columnIndex = _sortColumnIndex;
|
||||
if (columnIndex == null) {
|
||||
return items;
|
||||
}
|
||||
final sorted = List<ApprovalHistoryRecord>.from(items);
|
||||
sorted.sort((a, b) {
|
||||
int compare;
|
||||
switch (columnIndex) {
|
||||
case 0:
|
||||
compare = a.id.compareTo(b.id);
|
||||
break;
|
||||
case 1:
|
||||
compare = a.approvalNo.compareTo(b.approvalNo);
|
||||
break;
|
||||
case 2:
|
||||
final left = a.stepOrder ?? 0;
|
||||
final right = b.stepOrder ?? 0;
|
||||
compare = left.compareTo(right);
|
||||
break;
|
||||
case 3:
|
||||
compare = a.approver.name.compareTo(b.approver.name);
|
||||
break;
|
||||
case 4:
|
||||
compare = a.action.name.compareTo(b.action.name);
|
||||
break;
|
||||
case 5:
|
||||
compare = (a.fromStatus?.name ?? '').compareTo(
|
||||
b.fromStatus?.name ?? '',
|
||||
);
|
||||
break;
|
||||
case 6:
|
||||
compare = a.toStatus.name.compareTo(b.toStatus.name);
|
||||
break;
|
||||
case 7:
|
||||
compare = a.actionAt.compareTo(b.actionAt);
|
||||
break;
|
||||
default:
|
||||
compare = 0;
|
||||
}
|
||||
return _sortAscending ? compare : -compare;
|
||||
});
|
||||
return sorted;
|
||||
}
|
||||
|
||||
String _actionLabel(ApprovalHistoryActionFilter filter) {
|
||||
switch (filter) {
|
||||
case ApprovalHistoryActionFilter.all:
|
||||
@@ -324,11 +358,25 @@ class _ApprovalHistoryTable extends StatelessWidget {
|
||||
required this.histories,
|
||||
required this.dateFormat,
|
||||
required this.query,
|
||||
required this.pagination,
|
||||
required this.onPageChange,
|
||||
required this.onPageSizeChange,
|
||||
required this.isLoading,
|
||||
required this.sortableColumns,
|
||||
required this.sortState,
|
||||
required this.onSortChanged,
|
||||
});
|
||||
|
||||
final List<ApprovalHistoryRecord> histories;
|
||||
final DateFormat dateFormat;
|
||||
final String query;
|
||||
final SuperportTablePagination pagination;
|
||||
final ValueChanged<int> onPageChange;
|
||||
final ValueChanged<int> onPageSizeChange;
|
||||
final bool isLoading;
|
||||
final Set<int> sortableColumns;
|
||||
final SuperportTableSortState? sortState;
|
||||
final void Function(int columnIndex, bool ascending) onSortChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -402,6 +450,13 @@ class _ApprovalHistoryTable extends StatelessWidget {
|
||||
return const FixedTableSpanExtent(110);
|
||||
}
|
||||
},
|
||||
pagination: pagination,
|
||||
onPageChange: onPageChange,
|
||||
onPageSizeChange: onPageSizeChange,
|
||||
isLoading: isLoading,
|
||||
sortableColumns: sortableColumns,
|
||||
sortState: sortState,
|
||||
onSortChanged: onSortChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user