결재 단계 UI 보완 및 분석 통과

This commit is contained in:
JiWoong Sul
2025-09-25 17:18:08 +09:00
parent c31f6217ef
commit 6d6781f552
7 changed files with 99 additions and 103 deletions

View File

@@ -513,8 +513,8 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
const SizedBox(height: 8),
ShadTextarea(
controller: noteController,
minLines: 3,
maxLines: 5,
minHeight: 120,
maxHeight: 220,
),
if (requireNote)
Padding(

View File

@@ -1,6 +1,7 @@
import '../../../../core/common/models/paginated_result.dart';
import '../../../domain/entities/approval.dart';
import '../../data/dtos/approval_dto.dart';
import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/features/approvals/data/dtos/approval_dto.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval.dart';
import '../../domain/entities/approval_step_record.dart';
class ApprovalStepRecordDto {

View File

@@ -1,9 +1,9 @@
import 'package:dio/dio.dart';
import '../../../../core/common/models/paginated_result.dart';
import '../../../../core/network/api_client.dart';
import '../../domain/entities/approval_step_record.dart';
import '../../domain/repositories/approval_step_repository.dart';
import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/core/network/api_client.dart';
import 'package:superport_v2/features/approvals/step/domain/entities/approval_step_record.dart';
import 'package:superport_v2/features/approvals/step/domain/repositories/approval_step_repository.dart';
import '../dtos/approval_step_record_dto.dart';
class ApprovalStepRepositoryRemote implements ApprovalStepRepository {

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:intl/intl.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart' as lucide;
import 'package:shadcn_ui/shadcn_ui.dart';
@@ -321,6 +320,9 @@ class _ApprovalStepEnabledPageState extends State<_ApprovalStepEnabledPage> {
child: Align(
alignment: Alignment.centerRight,
child: ShadButton.outline(
key: ValueKey(
'step_detail_${step.id ?? record.approvalId}_${step.stepOrder}',
),
size: ShadButtonSize.sm,
onPressed: step.id == null
? null
@@ -382,9 +384,7 @@ class _ApprovalStepEnabledPageState extends State<_ApprovalStepEnabledPage> {
options.add(const _StatusOption(id: -1, name: '전체'));
for (final record in records) {
final status = record.step.status;
if (status.id != null) {
options.add(_StatusOption(id: status.id!, name: status.name));
}
options.add(_StatusOption(id: status.id, name: status.name));
}
return options.toList()..sort((a, b) => a.id.compareTo(b.id));
}
@@ -444,6 +444,15 @@ class _ApprovalStepEnabledPageState extends State<_ApprovalStepEnabledPage> {
'결재번호 ${detail.approvalNo}',
style: theme.textTheme.muted,
),
footer: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ShadButton.ghost(
onPressed: () => Navigator.of(dialogContext).pop(),
child: const Text('닫기'),
),
],
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
@@ -485,15 +494,6 @@ class _ApprovalStepEnabledPageState extends State<_ApprovalStepEnabledPage> {
],
),
),
footer: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ShadButton.ghost(
onPressed: () => Navigator.of(dialogContext).pop(),
child: const Text('닫기'),
),
],
),
),
);
},

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:intl/intl.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart' as lucide;
import 'package:shadcn_ui/shadcn_ui.dart';
@@ -442,6 +441,7 @@ class _ApprovalTemplateEnabledPageState
Future<bool?> _openTemplateForm({ApprovalTemplate? template}) async {
final isEdit = template != null;
final existingTemplate = template;
final codeController = TextEditingController(text: template?.code ?? '');
final nameController = TextEditingController(text: template?.name ?? '');
final descriptionController = TextEditingController(
@@ -587,77 +587,76 @@ class _ApprovalTemplateEnabledPageState
),
actions: [
ShadButton.ghost(
onPressed: isSaving ? null : () => Navigator.of(context).pop(false),
onPressed: () {
if (isSaving) return;
Navigator.of(context).pop(false);
},
child: const Text('취소'),
),
ShadButton(
onPressed: isSaving
? null
: () async {
final codeValue = codeController.text.trim();
final nameValue = nameController.text.trim();
if (!isEdit && codeValue.isEmpty) {
modalSetState?.call(() => errorText = '템플릿 코드를 입력하세요.');
return;
}
if (nameValue.isEmpty) {
modalSetState?.call(() => errorText = '템플릿명을 입력하세요.');
return;
}
final validation = _validateSteps(steps);
if (validation != null) {
modalSetState?.call(() => errorText = validation);
return;
}
modalSetState?.call(() => errorText = null);
final stepInputs = steps
.map(
(field) => ApprovalTemplateStepInput(
id: field.id,
stepOrder: int.parse(
field.orderController.text.trim(),
),
approverId: int.parse(
field.approverController.text.trim(),
),
note: field.noteController.text.trim().isEmpty
? null
: field.noteController.text.trim(),
),
)
.toList();
final input = ApprovalTemplateInput(
code: isEdit ? template!.code : codeValue,
name: nameValue,
description: descriptionController.text.trim().isEmpty
onPressed: () async {
if (isSaving) return;
final codeValue = codeController.text.trim();
final nameValue = nameController.text.trim();
if (!isEdit && codeValue.isEmpty) {
modalSetState?.call(() => errorText = '템플릿 코드를 입력하세요.');
return;
}
if (nameValue.isEmpty) {
modalSetState?.call(() => errorText = '템플릿명을 입력하세요.');
return;
}
final validation = _validateSteps(steps);
if (validation != null) {
modalSetState?.call(() => errorText = validation);
return;
}
modalSetState?.call(() => errorText = null);
final stepInputs = steps
.map(
(field) => ApprovalTemplateStepInput(
id: field.id,
stepOrder: int.parse(field.orderController.text.trim()),
approverId: int.parse(field.approverController.text.trim()),
note: field.noteController.text.trim().isEmpty
? null
: descriptionController.text.trim(),
note: noteController.text.trim().isEmpty
? null
: noteController.text.trim(),
isActive: statusNotifier.value,
);
modalSetState?.call(() => isSaving = true);
final success = isEdit
? await _controller.update(
template!.id,
input,
stepInputs,
)
: await _controller.create(input, stepInputs);
if (success != null && mounted) {
Navigator.of(context).pop(true);
} else {
modalSetState?.call(() => isSaving = false);
}
},
child: isSaving
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
: field.noteController.text.trim(),
),
)
: Text(isEdit ? '수정 완료' : '생성 완료'),
.toList();
final input = ApprovalTemplateInput(
code: isEdit ? existingTemplate?.code : codeValue,
name: nameValue,
description: descriptionController.text.trim().isEmpty
? null
: descriptionController.text.trim(),
note: noteController.text.trim().isEmpty
? null
: noteController.text.trim(),
isActive: statusNotifier.value,
);
if (isEdit && existingTemplate == null) {
modalSetState?.call(() => errorText = '템플릿 정보를 불러오지 못했습니다.');
modalSetState?.call(() => isSaving = false);
return;
}
modalSetState?.call(() => isSaving = true);
final success = isEdit && existingTemplate != null
? await _controller.update(
existingTemplate.id,
input,
stepInputs,
)
: await _controller.create(input, stepInputs);
if (success != null && mounted) {
Navigator.of(context).pop(true);
} else {
modalSetState?.call(() => isSaving = false);
}
},
child: Text(isEdit ? '수정 완료' : '생성 완료'),
),
],
);
@@ -780,7 +779,9 @@ class _StepEditorRow extends StatelessWidget {
margin: const EdgeInsets.symmetric(vertical: 6),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(color: theme.colorScheme.border.withOpacity(0.6)),
border: Border.all(
color: theme.colorScheme.border.withValues(alpha: 0.6),
),
borderRadius: BorderRadius.circular(12),
),
child: Column(