결재 템플릿 단계 적용 구현
- ApprovalTemplate 엔티티·DTO·원격 리포지토리 추가 - ApprovalController에 템플릿 로딩/적용 상태와 assignSteps 호출 연동 - ApprovalPage 단계 탭에 템플릿 선택 UI 및 적용 확인 다이얼로그 구현 - 템플릿 적용 단위 테스트와 IMPLEMENTATION_TASKS 현황 갱신
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
import 'package:superport_v2/core/constants/app_sections.dart';
|
||||
import 'package:superport_v2/widgets/app_layout.dart';
|
||||
import 'package:superport_v2/widgets/components/filter_bar.dart';
|
||||
|
||||
class InboundPage extends StatefulWidget {
|
||||
const InboundPage({super.key});
|
||||
|
||||
@@ -43,95 +47,67 @@ class _InboundPageState extends State<InboundPage> {
|
||||
final theme = ShadTheme.of(context);
|
||||
final filtered = _filteredRecords;
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
return AppLayout(
|
||||
title: '입고 관리',
|
||||
subtitle: '입고 처리, 라인 품목, 상태를 한 화면에서 확인하고 관리합니다.',
|
||||
breadcrumbs: const [
|
||||
AppBreadcrumbItem(label: '대시보드', path: dashboardRoutePath),
|
||||
AppBreadcrumbItem(label: '입·출고', path: '/inventory/inbound'),
|
||||
AppBreadcrumbItem(label: '입고'),
|
||||
],
|
||||
actions: [
|
||||
ShadButton(
|
||||
leading: const Icon(LucideIcons.plus, size: 16),
|
||||
onPressed: _handleCreate,
|
||||
child: const Text('입고 등록'),
|
||||
),
|
||||
ShadButton.outline(
|
||||
leading: const Icon(LucideIcons.pencil, size: 16),
|
||||
onPressed:
|
||||
_selectedRecord == null ? null : () => _handleEdit(_selectedRecord!),
|
||||
child: const Text('선택 항목 수정'),
|
||||
),
|
||||
],
|
||||
toolbar: FilterBar(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('입고 관리', style: theme.textTheme.h2),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'입고 처리, 라인 품목, 상태를 한 화면에서 확인하고 관리합니다.',
|
||||
style: theme.textTheme.muted,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
SizedBox(
|
||||
width: 260,
|
||||
child: ShadInput(
|
||||
controller: _searchController,
|
||||
placeholder: const Text('트랜잭션번호, 작성자, 제품 검색'),
|
||||
leading: const Icon(LucideIcons.search, size: 16),
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 220,
|
||||
child: ShadButton.outline(
|
||||
onPressed: _pickDateRange,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ShadButton(
|
||||
leading: const Icon(LucideIcons.plus, size: 16),
|
||||
onPressed: _handleCreate,
|
||||
child: const Text('입고 등록'),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
ShadButton.outline(
|
||||
leading: const Icon(LucideIcons.pencil, size: 16),
|
||||
onPressed: _selectedRecord == null
|
||||
? null
|
||||
: () => _handleEdit(_selectedRecord!),
|
||||
child: const Text('선택 항목 수정'),
|
||||
const Icon(LucideIcons.calendar, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
_dateRange == null
|
||||
? '기간 선택'
|
||||
: '${_dateFormatter.format(_dateRange!.start)} ~ ${_dateFormatter.format(_dateRange!.end)}',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ShadCard(
|
||||
title: Text('검색 필터', style: theme.textTheme.h3),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Wrap(
|
||||
spacing: 16,
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 260,
|
||||
child: ShadInput(
|
||||
controller: _searchController,
|
||||
placeholder: const Text('트랜잭션번호, 작성자, 제품 검색'),
|
||||
leading: const Icon(LucideIcons.search, size: 16),
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 220,
|
||||
child: ShadButton.outline(
|
||||
onPressed: _pickDateRange,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(LucideIcons.calendar, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
_dateRange == null
|
||||
? '기간 선택'
|
||||
: '${_dateFormatter.format(_dateRange!.start)} ~ ${_dateFormatter.format(_dateRange!.end)}',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_dateRange != null)
|
||||
ShadButton.ghost(
|
||||
onPressed: () => setState(() => _dateRange = null),
|
||||
child: const Text('기간 초기화'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (_dateRange != null)
|
||||
ShadButton.ghost(
|
||||
onPressed: () => setState(() => _dateRange = null),
|
||||
child: const Text('기간 초기화'),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ShadCard(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@@ -158,18 +134,21 @@ class _InboundPageState extends State<InboundPage> {
|
||||
.toList(),
|
||||
children: [
|
||||
for (final record in filtered)
|
||||
_buildRecordRow(record).map(
|
||||
(value) => ShadTableCell(
|
||||
child: Text(
|
||||
value,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildRecordRow(record)
|
||||
.map(
|
||||
(value) => ShadTableCell(
|
||||
child: Text(
|
||||
value,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
],
|
||||
columnSpanExtent: (index) =>
|
||||
const FixedTableSpanExtent(140),
|
||||
rowSpanExtent: (index) => const FixedTableSpanExtent(56),
|
||||
rowSpanExtent: (index) =>
|
||||
const FixedTableSpanExtent(56),
|
||||
onRowTap: (rowIndex) {
|
||||
setState(() {
|
||||
_selectedRecord = filtered[rowIndex];
|
||||
|
||||
Reference in New Issue
Block a user