결재 권한 테스트 및 인벤토리 위젯 안정화
This commit is contained in:
@@ -6,8 +6,13 @@ import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
import '../../../../core/config/environment.dart';
|
||||
import '../../../../core/constants/app_sections.dart';
|
||||
import '../../../../core/permissions/permission_manager.dart';
|
||||
import '../../../../widgets/app_layout.dart';
|
||||
import '../../../../widgets/components/feedback.dart';
|
||||
import '../../../../widgets/components/filter_bar.dart';
|
||||
import '../../../../widgets/components/superport_date_picker.dart';
|
||||
import '../../../../widgets/components/superport_dialog.dart';
|
||||
import '../../../../widgets/components/superport_table.dart';
|
||||
import '../../../../widgets/spec_page.dart';
|
||||
import '../../domain/entities/approval.dart';
|
||||
import '../../domain/entities/approval_template.dart';
|
||||
@@ -15,6 +20,8 @@ import '../../domain/repositories/approval_repository.dart';
|
||||
import '../../domain/repositories/approval_template_repository.dart';
|
||||
import '../controllers/approval_controller.dart';
|
||||
|
||||
const _approvalsResourcePath = '/approvals/requests';
|
||||
|
||||
class ApprovalPage extends StatelessWidget {
|
||||
const ApprovalPage({super.key});
|
||||
|
||||
@@ -129,9 +136,7 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
final error = _controller.errorMessage;
|
||||
if (error != null && error != _lastError && mounted) {
|
||||
_lastError = error;
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(error)));
|
||||
SuperportToast.error(context, error);
|
||||
_controller.clearError();
|
||||
}
|
||||
}
|
||||
@@ -148,6 +153,7 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
final permissionManager = PermissionScope.of(context);
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: _controller,
|
||||
@@ -171,6 +177,10 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
final isLoadingTemplates = _controller.isLoadingTemplates;
|
||||
final isApplyingTemplate = _controller.isApplyingTemplate;
|
||||
final applyingTemplateId = _controller.applyingTemplateId;
|
||||
final canPerformStepActions = permissionManager.can(
|
||||
_approvalsResourcePath,
|
||||
PermissionAction.approve,
|
||||
);
|
||||
|
||||
if (templates.isNotEmpty && _selectedTemplateId == null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
@@ -201,6 +211,17 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
),
|
||||
],
|
||||
toolbar: FilterBar(
|
||||
actionConfig: FilterBarActionConfig(
|
||||
onApply: _applyFilters,
|
||||
onReset: _resetFilters,
|
||||
hasPendingChanges: false,
|
||||
hasActiveFilters: _hasFilters(),
|
||||
applyEnabled: !_controller.isLoadingList,
|
||||
resetLabel: '필터 초기화',
|
||||
resetKey: const ValueKey('approval_filter_reset'),
|
||||
resetEnabled: !_controller.isLoadingList && _hasFilters(),
|
||||
showReset: true,
|
||||
),
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 260,
|
||||
@@ -237,21 +258,24 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
),
|
||||
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
|
||||
? '기간 선택'
|
||||
: '${_dateFormat.format(_dateRange!.start)} ~ ${_dateFormat.format(_dateRange!.end)}',
|
||||
child: SuperportDateRangePickerButton(
|
||||
value: _dateRange,
|
||||
dateFormat: _dateFormat,
|
||||
enabled: !_controller.isLoadingList,
|
||||
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)
|
||||
@@ -263,24 +287,6 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
},
|
||||
child: const Text('기간 초기화'),
|
||||
),
|
||||
ShadButton.outline(
|
||||
onPressed: _controller.isLoadingList ? null : _applyFilters,
|
||||
child: const Text('검색 적용'),
|
||||
),
|
||||
ShadButton.ghost(
|
||||
key: const ValueKey('approval_filter_reset'),
|
||||
onPressed: _controller.isLoadingList
|
||||
? null
|
||||
: () {
|
||||
if (!_hasFilters()) return;
|
||||
_searchController.clear();
|
||||
_searchFocus.requestFocus();
|
||||
_dateRange = null;
|
||||
_controller.clearFilters();
|
||||
_controller.fetch(page: 1);
|
||||
},
|
||||
child: const Text('필터 초기화'),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
@@ -360,6 +366,7 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
isApplyingTemplate: isApplyingTemplate,
|
||||
applyingTemplateId: applyingTemplateId,
|
||||
selectedTemplateId: _selectedTemplateId,
|
||||
canPerformStepActions: canPerformStepActions,
|
||||
dateFormat: _dateTimeFormat,
|
||||
onRefresh: () {
|
||||
final id = selectedApproval?.id;
|
||||
@@ -390,33 +397,20 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
_controller.fetch(page: 1);
|
||||
}
|
||||
|
||||
void _resetFilters() {
|
||||
_searchController.clear();
|
||||
_searchFocus.requestFocus();
|
||||
_dateRange = null;
|
||||
_controller.clearFilters();
|
||||
_controller.fetch(page: 1);
|
||||
}
|
||||
|
||||
bool _hasFilters() {
|
||||
return _searchController.text.isNotEmpty ||
|
||||
_controller.statusFilter != ApprovalStatusFilter.all ||
|
||||
_dateRange != null;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleStepAction(
|
||||
ApprovalStep step,
|
||||
ApprovalStepActionType type,
|
||||
@@ -433,9 +427,7 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
if (!mounted || !success) {
|
||||
return;
|
||||
}
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(_successMessage(type))));
|
||||
SuperportToast.success(context, _successMessage(type));
|
||||
}
|
||||
|
||||
void _handleSelectTemplate(int? templateId) {
|
||||
@@ -451,9 +443,7 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
}
|
||||
}
|
||||
if (template == null) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('선택한 템플릿 정보를 찾을 수 없습니다.')));
|
||||
SuperportToast.error(context, '선택한 템플릿 정보를 찾을 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -467,9 +457,7 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
return;
|
||||
}
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('템플릿 "${template.name}"을(를) 적용했습니다.')),
|
||||
);
|
||||
SuperportToast.success(context, '템플릿 "${template.name}"을(를) 적용했습니다.');
|
||||
}
|
||||
|
||||
Future<_StepActionDialogResult?> _showStepActionDialog(
|
||||
@@ -486,63 +474,15 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
builder: (context, setState) {
|
||||
final materialTheme = Theme.of(context);
|
||||
final shadTheme = ShadTheme.of(context);
|
||||
return AlertDialog(
|
||||
title: Text(_dialogTitle(type)),
|
||||
content: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 420),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'선택 단계: Step ${step.stepOrder}',
|
||||
style: shadTheme.textTheme.small,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'승인자: ${step.approver.name}',
|
||||
style: shadTheme.textTheme.small,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'현재 상태: ${step.status.name}',
|
||||
style: shadTheme.textTheme.small,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text('비고', style: shadTheme.textTheme.small),
|
||||
const SizedBox(height: 8),
|
||||
ShadTextarea(
|
||||
controller: noteController,
|
||||
minHeight: 120,
|
||||
maxHeight: 220,
|
||||
),
|
||||
if (requireNote)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: Text(
|
||||
'코멘트에는 비고 입력이 필요합니다.',
|
||||
style: shadTheme.textTheme.muted,
|
||||
),
|
||||
),
|
||||
if (errorText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
errorText!,
|
||||
style: shadTheme.textTheme.small.copyWith(
|
||||
color: materialTheme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
return SuperportDialog(
|
||||
title: _dialogTitle(type),
|
||||
constraints: const BoxConstraints(maxWidth: 420),
|
||||
actions: [
|
||||
TextButton(
|
||||
ShadButton.ghost(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||
child: const Text('취소'),
|
||||
),
|
||||
FilledButton(
|
||||
ShadButton(
|
||||
onPressed: () {
|
||||
final note = noteController.text.trim();
|
||||
if (requireNote && note.isEmpty) {
|
||||
@@ -556,6 +496,52 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
child: Text(_dialogConfirmLabel(type)),
|
||||
),
|
||||
],
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'선택 단계: Step ${step.stepOrder}',
|
||||
style: shadTheme.textTheme.small,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'승인자: ${step.approver.name}',
|
||||
style: shadTheme.textTheme.small,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'현재 상태: ${step.status.name}',
|
||||
style: shadTheme.textTheme.small,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text('비고', style: shadTheme.textTheme.small),
|
||||
const SizedBox(height: 8),
|
||||
ShadTextarea(
|
||||
controller: noteController,
|
||||
minHeight: 120,
|
||||
maxHeight: 220,
|
||||
),
|
||||
if (requireNote)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: Text(
|
||||
'코멘트에는 비고 입력이 필요합니다.',
|
||||
style: shadTheme.textTheme.muted,
|
||||
),
|
||||
),
|
||||
if (errorText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
errorText!,
|
||||
style: shadTheme.textTheme.small.copyWith(
|
||||
color: materialTheme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -576,24 +562,22 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
|
||||
buffer.write('\n설명: $description');
|
||||
}
|
||||
|
||||
final confirmed = await showDialog<bool>(
|
||||
final confirmed = await SuperportDialog.show<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertDialog(
|
||||
title: const Text('템플릿 적용 확인'),
|
||||
content: Text(buffer.toString()),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(false),
|
||||
child: const Text('취소'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(true),
|
||||
child: const Text('적용'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
dialog: SuperportDialog(
|
||||
title: '템플릿 적용 확인',
|
||||
actions: [
|
||||
ShadButton.ghost(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('취소'),
|
||||
),
|
||||
ShadButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: const Text('적용'),
|
||||
),
|
||||
],
|
||||
child: Text(buffer.toString()),
|
||||
),
|
||||
);
|
||||
return confirmed ?? false;
|
||||
}
|
||||
@@ -722,9 +706,11 @@ class _ApprovalTable extends StatelessWidget {
|
||||
rows.add(cells);
|
||||
}
|
||||
|
||||
return ShadTable.list(
|
||||
return SuperportTable.fromCells(
|
||||
header: header,
|
||||
children: rows,
|
||||
rows: rows,
|
||||
rowHeight: 56,
|
||||
maxHeight: 520,
|
||||
columnSpanExtent: (index) {
|
||||
switch (index) {
|
||||
case 1:
|
||||
@@ -758,6 +744,7 @@ class _DetailSection extends StatelessWidget {
|
||||
required this.isApplyingTemplate,
|
||||
required this.applyingTemplateId,
|
||||
required this.selectedTemplateId,
|
||||
required this.canPerformStepActions,
|
||||
required this.dateFormat,
|
||||
required this.onRefresh,
|
||||
required this.onClose,
|
||||
@@ -778,6 +765,7 @@ class _DetailSection extends StatelessWidget {
|
||||
final bool isApplyingTemplate;
|
||||
final int? applyingTemplateId;
|
||||
final int? selectedTemplateId;
|
||||
final bool canPerformStepActions;
|
||||
final intl.DateFormat dateFormat;
|
||||
final VoidCallback onRefresh;
|
||||
final VoidCallback? onClose;
|
||||
@@ -856,6 +844,7 @@ class _DetailSection extends StatelessWidget {
|
||||
isApplyingTemplate: isApplyingTemplate,
|
||||
applyingTemplateId: applyingTemplateId,
|
||||
selectedTemplateId: selectedTemplateId,
|
||||
canPerformStepActions: canPerformStepActions,
|
||||
onSelectTemplate: onSelectTemplate,
|
||||
onApplyTemplate: onApplyTemplate,
|
||||
onReloadTemplates: onReloadTemplates,
|
||||
@@ -952,6 +941,7 @@ class _StepTab extends StatelessWidget {
|
||||
required this.isApplyingTemplate,
|
||||
required this.applyingTemplateId,
|
||||
required this.selectedTemplateId,
|
||||
required this.canPerformStepActions,
|
||||
required this.onSelectTemplate,
|
||||
required this.onApplyTemplate,
|
||||
required this.onReloadTemplates,
|
||||
@@ -970,6 +960,7 @@ class _StepTab extends StatelessWidget {
|
||||
final bool isApplyingTemplate;
|
||||
final int? applyingTemplateId;
|
||||
final int? selectedTemplateId;
|
||||
final bool canPerformStepActions;
|
||||
final void Function(int?) onSelectTemplate;
|
||||
final void Function(int templateId) onApplyTemplate;
|
||||
final VoidCallback onReloadTemplates;
|
||||
@@ -989,6 +980,7 @@ class _StepTab extends StatelessWidget {
|
||||
selectedTemplateId: selectedTemplateId,
|
||||
isApplyingTemplate: isApplyingTemplate,
|
||||
applyingTemplateId: applyingTemplateId,
|
||||
canApplyTemplate: canPerformStepActions,
|
||||
onSelectTemplate: onSelectTemplate,
|
||||
onApplyTemplate: onApplyTemplate,
|
||||
onReload: onReloadTemplates,
|
||||
@@ -1002,6 +994,14 @@ class _StepTab extends StatelessWidget {
|
||||
style: theme.textTheme.muted,
|
||||
),
|
||||
),
|
||||
if (!canPerformStepActions)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 12, 20, 8),
|
||||
child: Text(
|
||||
'결재 권한이 없어 단계 행위를 실행할 수 없습니다.',
|
||||
style: theme.textTheme.muted,
|
||||
),
|
||||
),
|
||||
if (steps.isEmpty)
|
||||
Expanded(
|
||||
child: Center(
|
||||
@@ -1014,7 +1014,10 @@ class _StepTab extends StatelessWidget {
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
|
||||
itemBuilder: (context, index) {
|
||||
final step = steps[index];
|
||||
final disabledReason = _disabledReason(step);
|
||||
final disabledReason = _disabledReason(
|
||||
step,
|
||||
canPerformStepActions,
|
||||
);
|
||||
final isProcessingStep =
|
||||
isPerformingAction && processingStepId == step.id;
|
||||
final isEnabled = disabledReason == null && !isProcessingStep;
|
||||
@@ -1186,7 +1189,10 @@ class _StepTab extends StatelessWidget {
|
||||
return button;
|
||||
}
|
||||
|
||||
String? _disabledReason(ApprovalStep step) {
|
||||
String? _disabledReason(ApprovalStep step, bool canPerformStepActions) {
|
||||
if (!canPerformStepActions) {
|
||||
return '결재 행위를 수행할 권한이 없습니다.';
|
||||
}
|
||||
if (isLoadingActions) {
|
||||
return '행위 목록을 불러오는 중입니다.';
|
||||
}
|
||||
@@ -1251,6 +1257,7 @@ class _TemplateToolbar extends StatelessWidget {
|
||||
required this.selectedTemplateId,
|
||||
required this.isApplyingTemplate,
|
||||
required this.applyingTemplateId,
|
||||
required this.canApplyTemplate,
|
||||
required this.onSelectTemplate,
|
||||
required this.onApplyTemplate,
|
||||
required this.onReload,
|
||||
@@ -1261,6 +1268,7 @@ class _TemplateToolbar extends StatelessWidget {
|
||||
final int? selectedTemplateId;
|
||||
final bool isApplyingTemplate;
|
||||
final int? applyingTemplateId;
|
||||
final bool canApplyTemplate;
|
||||
final void Function(int?) onSelectTemplate;
|
||||
final void Function(int templateId) onApplyTemplate;
|
||||
final VoidCallback onReload;
|
||||
@@ -1272,11 +1280,45 @@ class _TemplateToolbar extends StatelessWidget {
|
||||
final isApplyingCurrent =
|
||||
isApplyingTemplate && applyingTemplateId == selectedTemplateId;
|
||||
final canApply =
|
||||
canApplyTemplate &&
|
||||
templates.isNotEmpty &&
|
||||
!isLoading &&
|
||||
selectedTemplateId != null &&
|
||||
!isApplyingTemplate;
|
||||
|
||||
Widget applyButton = ShadButton(
|
||||
onPressed: canApply
|
||||
? () {
|
||||
final templateId = selectedTemplateId;
|
||||
if (templateId != null) {
|
||||
onApplyTemplate(templateId);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isApplyingCurrent) ...[
|
||||
SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: const CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text('적용 중...'),
|
||||
] else ...[
|
||||
const Icon(lucide.LucideIcons.layoutList, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
const Text('템플릿 적용'),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (!canApplyTemplate) {
|
||||
applyButton = Tooltip(message: '템플릿을 적용할 권한이 없습니다.', child: applyButton);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -1287,7 +1329,7 @@ class _TemplateToolbar extends StatelessWidget {
|
||||
key: ValueKey(templates.length),
|
||||
placeholder: const Text('템플릿 선택'),
|
||||
initialValue: selectedTemplateId,
|
||||
onChanged: onSelectTemplate,
|
||||
onChanged: canApplyTemplate ? onSelectTemplate : null,
|
||||
selectedOptionBuilder: (context, value) {
|
||||
final match = _findTemplate(value);
|
||||
return Text(match?.name ?? '템플릿 선택');
|
||||
@@ -1315,36 +1357,14 @@ class _TemplateToolbar extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
ShadButton(
|
||||
onPressed: canApply
|
||||
? () {
|
||||
final templateId = selectedTemplateId;
|
||||
if (templateId != null) {
|
||||
onApplyTemplate(templateId);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isApplyingCurrent) ...[
|
||||
SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: const CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text('적용 중...'),
|
||||
] else ...[
|
||||
const Icon(lucide.LucideIcons.layoutList, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
const Text('템플릿 적용'),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
applyButton,
|
||||
],
|
||||
),
|
||||
if (!canApplyTemplate)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text('결재 템플릿 적용 권한이 없습니다.', style: theme.textTheme.muted),
|
||||
),
|
||||
if (isLoading)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
|
||||
Reference in New Issue
Block a user