대시보드 결재 상세 진입 지원
This commit is contained in:
@@ -345,6 +345,9 @@ class ApprovalController extends ChangeNotifier {
|
||||
}
|
||||
} catch (error) {
|
||||
final failure = Failure.from(error);
|
||||
debugPrint(
|
||||
'[ApprovalController] 결재 상세 조회 실패: ${failure.describe()}',
|
||||
); // 에러 발생 시 콘솔에 남겨 즉시 파악할 수 있도록 한다.
|
||||
_errorMessage = failure.describe();
|
||||
} finally {
|
||||
_isLoadingDetail = false;
|
||||
|
||||
@@ -28,7 +28,9 @@ const _approvalsResourcePath = PermissionResources.approvals;
|
||||
///
|
||||
/// 기능 플래그에 따라 실제 화면 또는 비활성 안내 화면을 보여준다.
|
||||
class ApprovalPage extends StatelessWidget {
|
||||
const ApprovalPage({super.key});
|
||||
const ApprovalPage({super.key, this.routeUri});
|
||||
|
||||
final Uri? routeUri;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -59,13 +61,15 @@ class ApprovalPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
return const _ApprovalEnabledPage();
|
||||
return _ApprovalEnabledPage(routeUri: routeUri);
|
||||
}
|
||||
}
|
||||
|
||||
/// 결재 기능이 활성화되었을 때 사용되는 실제 페이지 위젯.
|
||||
class _ApprovalEnabledPage extends StatefulWidget {
|
||||
const _ApprovalEnabledPage();
|
||||
const _ApprovalEnabledPage({this.routeUri});
|
||||
|
||||
final Uri? routeUri;
|
||||
|
||||
@override
|
||||
State<_ApprovalEnabledPage> createState() => _ApprovalEnabledPageState();
|
||||
@@ -76,14 +80,17 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
final TextEditingController _transactionController = TextEditingController();
|
||||
final TextEditingController _requesterController = TextEditingController();
|
||||
final FocusNode _transactionFocus = FocusNode();
|
||||
final GlobalKey _detailSectionKey = GlobalKey();
|
||||
InventoryEmployeeSuggestion? _selectedRequester;
|
||||
final intl.DateFormat _dateTimeFormat = intl.DateFormat('yyyy-MM-dd HH:mm');
|
||||
String? _lastError;
|
||||
int? _selectedTemplateId;
|
||||
String? _pendingRouteSelection;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_pendingRouteSelection = _parseRouteSelection(widget.routeUri);
|
||||
_controller = ApprovalController(
|
||||
approvalRepository: GetIt.I<ApprovalRepository>(),
|
||||
templateRepository: GetIt.I<ApprovalTemplateRepository>(),
|
||||
@@ -98,9 +105,24 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
_controller.loadStatusLookups(),
|
||||
]);
|
||||
await _controller.fetch();
|
||||
_applyRouteSelectionIfNeeded(
|
||||
_controller.result?.items ?? const <Approval>[],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant _ApprovalEnabledPage oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.routeUri != widget.routeUri) {
|
||||
_pendingRouteSelection = _parseRouteSelection(widget.routeUri);
|
||||
final currentResult = _controller.result;
|
||||
if (currentResult != null) {
|
||||
_applyRouteSelectionIfNeeded(currentResult.items);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handleControllerUpdate() {
|
||||
final error = _controller.errorMessage;
|
||||
if (error != null && error != _lastError && mounted) {
|
||||
@@ -110,6 +132,57 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 라우트 쿼리에서 선택된 결재번호를 읽어온다.
|
||||
String? _parseRouteSelection(Uri? routeUri) {
|
||||
final value = routeUri?.queryParameters['selected']?.trim();
|
||||
if (value == null || value.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// 최초 로딩 시 라우트에서 전달된 결재번호가 있으면 자동으로 상세를 연다.
|
||||
void _applyRouteSelectionIfNeeded(List<Approval> approvals) {
|
||||
final target = _pendingRouteSelection;
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
for (final approval in approvals) {
|
||||
if (approval.approvalNo == target && approval.id != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
_selectApproval(approval.id!);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
_pendingRouteSelection = null;
|
||||
}
|
||||
|
||||
Future<void> _selectApproval(int id, {bool reveal = true}) async {
|
||||
await _controller.selectApproval(id);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
if (!reveal) {
|
||||
return;
|
||||
}
|
||||
await _revealDetailSection();
|
||||
}
|
||||
|
||||
Future<void> _revealDetailSection() async {
|
||||
final detailContext = _detailSectionKey.currentContext;
|
||||
if (detailContext == null) {
|
||||
return;
|
||||
}
|
||||
await Scrollable.ensureVisible(
|
||||
detailContext,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
alignment: 0.05,
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.removeListener(_handleControllerUpdate);
|
||||
@@ -130,6 +203,9 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
builder: (context, _) {
|
||||
final result = _controller.result;
|
||||
final approvals = result?.items ?? const <Approval>[];
|
||||
if (result != null) {
|
||||
_applyRouteSelectionIfNeeded(approvals);
|
||||
}
|
||||
final selectedApproval = _controller.selected;
|
||||
final totalCount = result?.total ?? 0;
|
||||
final currentPage = result?.page ?? 1;
|
||||
@@ -279,8 +355,8 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
size: ShadButtonSize.sm,
|
||||
onPressed:
|
||||
_controller.isLoadingList || currentPage <= 1
|
||||
? null
|
||||
: () => _controller.fetch(page: 1),
|
||||
? null
|
||||
: () => _controller.fetch(page: 1),
|
||||
child: const Text('처음'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -304,9 +380,10 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
ShadButton.outline(
|
||||
size: ShadButtonSize.sm,
|
||||
onPressed:
|
||||
_controller.isLoadingList || currentPage >= totalPages
|
||||
? null
|
||||
: () => _controller.fetch(page: totalPages),
|
||||
_controller.isLoadingList ||
|
||||
currentPage >= totalPages
|
||||
? null
|
||||
: () => _controller.fetch(page: totalPages),
|
||||
child: const Text('마지막'),
|
||||
),
|
||||
],
|
||||
@@ -332,13 +409,14 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
onView: (approval) {
|
||||
final id = approval.id;
|
||||
if (id != null) {
|
||||
_controller.selectApproval(id);
|
||||
_selectApproval(id);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
_DetailSection(
|
||||
key: _detailSectionKey,
|
||||
approval: selectedApproval,
|
||||
isLoading: _controller.isLoadingDetail,
|
||||
isLoadingActions: isLoadingActions,
|
||||
@@ -358,7 +436,7 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
onRefresh: () {
|
||||
final id = selectedApproval?.id;
|
||||
if (id != null) {
|
||||
_controller.selectApproval(id);
|
||||
_selectApproval(id, reveal: false);
|
||||
}
|
||||
},
|
||||
onClose: selectedApproval == null
|
||||
@@ -411,9 +489,9 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
onPressed: isSubmitting
|
||||
? null
|
||||
: () => Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).pop(false),
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).pop(false),
|
||||
child: const Text('취소'),
|
||||
),
|
||||
ShadButton(
|
||||
@@ -678,10 +756,7 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
|
||||
if (created == true && mounted) {
|
||||
final number = createdApprovalNo ?? '-';
|
||||
SuperportToast.success(
|
||||
context,
|
||||
'결재를 생성했습니다. ($number)',
|
||||
);
|
||||
SuperportToast.success(context, '결재를 생성했습니다. ($number)');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -798,10 +873,8 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
constraints: const BoxConstraints(maxWidth: 420),
|
||||
actions: [
|
||||
ShadButton.ghost(
|
||||
onPressed: () => Navigator.of(
|
||||
dialogContext,
|
||||
rootNavigator: true,
|
||||
).pop(),
|
||||
onPressed: () =>
|
||||
Navigator.of(dialogContext, rootNavigator: true).pop(),
|
||||
child: const Text('취소'),
|
||||
),
|
||||
ShadButton(
|
||||
@@ -811,10 +884,7 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
setState(() => errorText = '비고를 입력하세요.');
|
||||
return;
|
||||
}
|
||||
Navigator.of(
|
||||
dialogContext,
|
||||
rootNavigator: true,
|
||||
).pop(
|
||||
Navigator.of(dialogContext, rootNavigator: true).pop(
|
||||
_StepActionDialogResult(note: note.isEmpty ? null : note),
|
||||
);
|
||||
},
|
||||
@@ -1049,6 +1119,7 @@ class _ApprovalTable extends StatelessWidget {
|
||||
/// 선택 상태와 로딩 여부에 따라 안내 문구 또는 상세 정보를 노출한다.
|
||||
class _DetailSection extends StatelessWidget {
|
||||
const _DetailSection({
|
||||
super.key,
|
||||
required this.approval,
|
||||
required this.isLoading,
|
||||
required this.isLoadingActions,
|
||||
|
||||
Reference in New Issue
Block a user