feat(dialog): 상세 팝업 SuperportDetailDialog 통합
- SuperportDetailDialog 위젯과 showSuperportDetailDialog 헬퍼를 추가하고 metadata/섹션 패턴을 표준화 - 결재/재고/마스터 각 상세 다이얼로그를 dialogs 디렉터리에 신설하고 기존 페이지를 신규 팝업으로 전환 - SuperportTable 행 선택과 우편번호 검색 다이얼로그 onRowTap 보정을 통해 헤더 오프셋 버그를 제거 - 상세 다이얼로그 및 트랜잭션/상세 뷰 전용 위젯 테스트와 tester_extensions 유틸을 추가하여 회귀를 방지 - detail_dialog_unification_plan.md로 작업 배경과 필드 통합 계획을 문서화
This commit is contained in:
@@ -45,6 +45,10 @@ import '../widgets/inbound_detail_view.dart';
|
||||
|
||||
const String _inboundTransactionTypeId = '입고';
|
||||
|
||||
class _InboundDetailSections {
|
||||
static const lines = 'lines';
|
||||
}
|
||||
|
||||
/// 입고 목록과 상세/등록 모달을 관리하는 화면.
|
||||
class InboundPage extends StatefulWidget {
|
||||
const InboundPage({super.key, required this.routeUri});
|
||||
@@ -711,7 +715,10 @@ class _InboundPageState extends State<InboundPage> {
|
||||
rowSpanExtent: (index) =>
|
||||
const FixedTableSpanExtent(InboundTableSpec.rowSpanHeight),
|
||||
onRowTap: (rowIndex) {
|
||||
final record = records[rowIndex];
|
||||
if (rowIndex <= 0 || rowIndex > records.length) {
|
||||
return;
|
||||
}
|
||||
final record = records[rowIndex - 1];
|
||||
_selectRecord(record, openDetail: true);
|
||||
},
|
||||
);
|
||||
@@ -771,18 +778,136 @@ class _InboundPageState extends State<InboundPage> {
|
||||
await showInventoryTransactionDetailDialog<void>(
|
||||
context: context,
|
||||
title: '입고 상세',
|
||||
transactionNumber: record.transactionNumber,
|
||||
body: InboundDetailView(
|
||||
record: record,
|
||||
dateFormatter: _dateFormatter,
|
||||
currencyFormatter: _currencyFormatter,
|
||||
transitionsEnabled: _transitionsEnabled,
|
||||
),
|
||||
description: '입고 트랜잭션과 라인 품목을 확인하세요.',
|
||||
summary: _buildInboundDetailSummary(record),
|
||||
summaryBadges: _buildInboundDetailBadges(record),
|
||||
metadata: _buildInboundDetailMetadata(record),
|
||||
sections: [
|
||||
SuperportDetailDialogSection(
|
||||
id: _InboundDetailSections.lines,
|
||||
label: '라인 품목',
|
||||
icon: lucide.LucideIcons.listChecks,
|
||||
builder: (_) => InboundDetailView(
|
||||
record: record,
|
||||
currencyFormatter: _currencyFormatter,
|
||||
transitionsEnabled: _transitionsEnabled,
|
||||
),
|
||||
),
|
||||
],
|
||||
initialSectionId: _InboundDetailSections.lines,
|
||||
actions: _buildDetailActions(record),
|
||||
constraints: const BoxConstraints(maxWidth: 920),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInboundDetailSummary(InboundRecord record) {
|
||||
final theme = ShadTheme.of(context);
|
||||
final processedAt = _dateFormatter.format(record.processedAt);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(record.transactionNumber, style: theme.textTheme.h4),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'$processedAt · ${record.transactionType}',
|
||||
style: theme.textTheme.muted,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildInboundDetailBadges(InboundRecord record) {
|
||||
final badges = <Widget>[ShadBadge(child: Text(record.status))];
|
||||
badges.add(
|
||||
record.isActive
|
||||
? const ShadBadge.outline(child: Text('사용중'))
|
||||
: const ShadBadge.destructive(child: Text('미사용')),
|
||||
);
|
||||
if (record.partnerName != null && record.partnerName!.trim().isNotEmpty) {
|
||||
badges.add(ShadBadge.outline(child: Text(record.partnerName!.trim())));
|
||||
}
|
||||
return badges;
|
||||
}
|
||||
|
||||
List<SuperportDetailMetadata> _buildInboundDetailMetadata(
|
||||
InboundRecord record,
|
||||
) {
|
||||
final partnerSummary = _formatPartnerSummary(record);
|
||||
return [
|
||||
SuperportDetailMetadata.text(
|
||||
label: '트랜잭션번호',
|
||||
value: record.transactionNumber,
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '처리일자',
|
||||
value: _dateFormatter.format(record.processedAt),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '트랜잭션 유형',
|
||||
value: record.transactionType,
|
||||
),
|
||||
SuperportDetailMetadata.text(label: '상태', value: record.status),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '사용 상태',
|
||||
value: record.isActive ? '사용중' : '미사용',
|
||||
),
|
||||
SuperportDetailMetadata.text(label: '작성자', value: record.writer),
|
||||
SuperportDetailMetadata.text(label: '창고', value: record.warehouse),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '창고 코드',
|
||||
value: _dashIfEmpty(record.warehouseCode),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '창고 우편번호',
|
||||
value: _dashIfEmpty(record.warehouseZipcode),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '창고 주소',
|
||||
value: _dashIfEmpty(record.warehouseAddress),
|
||||
),
|
||||
SuperportDetailMetadata.text(label: '파트너사', value: partnerSummary),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '파트너 코드',
|
||||
value: _dashIfEmpty(record.partnerCode),
|
||||
),
|
||||
SuperportDetailMetadata.text(label: '품목 수', value: '${record.itemCount}'),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '총 수량',
|
||||
value: '${record.totalQuantity}',
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '총 금액',
|
||||
value: _currencyFormatter.format(record.totalAmount),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '예상 반납일',
|
||||
value: record.expectedReturnDate == null
|
||||
? '-'
|
||||
: _dateFormatter.format(record.expectedReturnDate!),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '비고',
|
||||
value: _dashIfEmpty(record.remark),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
String _formatPartnerSummary(InboundRecord record) {
|
||||
final name = _dashIfEmpty(record.partnerName);
|
||||
final code = _dashIfEmpty(record.partnerCode);
|
||||
if (name == '-') {
|
||||
return '-';
|
||||
}
|
||||
return code == '-' ? name : '$name ($code)';
|
||||
}
|
||||
|
||||
String _dashIfEmpty(String? value) {
|
||||
if (value == null) {
|
||||
return '-';
|
||||
}
|
||||
final trimmed = value.trim();
|
||||
return trimmed.isEmpty ? '-' : trimmed;
|
||||
}
|
||||
|
||||
List<Widget> _buildDetailActions(InboundRecord record) {
|
||||
final isProcessing = _isProcessing(record.id) || _isLoading;
|
||||
final actions = <Widget>[];
|
||||
|
||||
@@ -9,13 +9,11 @@ class InboundDetailView extends StatelessWidget {
|
||||
const InboundDetailView({
|
||||
super.key,
|
||||
required this.record,
|
||||
required this.dateFormatter,
|
||||
required this.currencyFormatter,
|
||||
this.transitionsEnabled = true,
|
||||
});
|
||||
|
||||
final InboundRecord record;
|
||||
final intl.DateFormat dateFormatter;
|
||||
final intl.NumberFormat currencyFormatter;
|
||||
final bool transitionsEnabled;
|
||||
|
||||
@@ -31,44 +29,6 @@ class InboundDetailView extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
_DetailChip(
|
||||
label: '처리일자',
|
||||
value: dateFormatter.format(record.processedAt),
|
||||
),
|
||||
_DetailChip(label: '창고', value: record.warehouse),
|
||||
if (record.warehouseCode != null &&
|
||||
record.warehouseCode!.trim().isNotEmpty)
|
||||
_DetailChip(label: '창고 코드', value: record.warehouseCode!),
|
||||
if (record.warehouseZipcode != null &&
|
||||
record.warehouseZipcode!.trim().isNotEmpty)
|
||||
_DetailChip(label: '창고 우편번호', value: record.warehouseZipcode!),
|
||||
if (record.warehouseAddress != null &&
|
||||
record.warehouseAddress!.trim().isNotEmpty)
|
||||
_DetailChip(label: '창고 주소', value: record.warehouseAddress!),
|
||||
_DetailChip(label: '트랜잭션 유형', value: record.transactionType),
|
||||
_DetailChip(label: '상태', value: record.status),
|
||||
_DetailChip(label: '작성자', value: record.writer),
|
||||
if (record.partnerName != null &&
|
||||
record.partnerName!.trim().isNotEmpty)
|
||||
_DetailChip(label: '파트너사', value: record.partnerName!.trim()),
|
||||
if (record.partnerCode != null &&
|
||||
record.partnerCode!.trim().isNotEmpty)
|
||||
_DetailChip(label: '파트너 코드', value: record.partnerCode!.trim()),
|
||||
_DetailChip(label: '품목 수', value: '${record.itemCount}'),
|
||||
_DetailChip(label: '총 수량', value: '${record.totalQuantity}'),
|
||||
_DetailChip(
|
||||
label: '총 금액',
|
||||
value: currencyFormatter.format(record.totalAmount),
|
||||
),
|
||||
if (record.remark.isNotEmpty)
|
||||
_DetailChip(label: '비고', value: record.remark),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text('라인 품목', style: theme.textTheme.h4),
|
||||
const SizedBox(height: 8),
|
||||
_buildLineTable(),
|
||||
@@ -108,36 +68,3 @@ class InboundDetailView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DetailChip extends StatelessWidget {
|
||||
const _DetailChip({required this.label, required this.value});
|
||||
|
||||
final String label;
|
||||
final String value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.card,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: theme.colorScheme.border),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: theme.textTheme.small,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(value, style: theme.textTheme.p, textAlign: TextAlign.center),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@ import '../widgets/outbound_detail_view.dart';
|
||||
|
||||
const String _outboundTransactionTypeId = '출고';
|
||||
|
||||
class _OutboundDetailSections {
|
||||
static const lines = 'lines';
|
||||
}
|
||||
|
||||
/// 출고 목록과 등록/수정 모달을 관리하는 페이지.
|
||||
class OutboundPage extends StatefulWidget {
|
||||
const OutboundPage({super.key, required this.routeUri});
|
||||
@@ -650,7 +654,11 @@ class _OutboundPageState extends State<OutboundPage> {
|
||||
OutboundTableSpec.rowSpanHeight,
|
||||
),
|
||||
onRowTap: (rowIndex) {
|
||||
final record = visibleRecords[rowIndex];
|
||||
if (rowIndex <= 0 ||
|
||||
rowIndex > visibleRecords.length) {
|
||||
return;
|
||||
}
|
||||
final record = visibleRecords[rowIndex - 1];
|
||||
_selectRecord(record, openDetail: true);
|
||||
},
|
||||
);
|
||||
@@ -870,18 +878,109 @@ class _OutboundPageState extends State<OutboundPage> {
|
||||
await showInventoryTransactionDetailDialog<void>(
|
||||
context: context,
|
||||
title: '출고 상세',
|
||||
transactionNumber: record.transactionNumber,
|
||||
body: OutboundDetailView(
|
||||
record: record,
|
||||
dateFormatter: _dateFormatter,
|
||||
currencyFormatter: _currencyFormatter,
|
||||
transitionsEnabled: _transitionsEnabled,
|
||||
),
|
||||
description: '출고 고객사와 라인 품목 정보를 확인하세요.',
|
||||
summary: _buildOutboundDetailSummary(record),
|
||||
summaryBadges: _buildOutboundDetailBadges(record),
|
||||
metadata: _buildOutboundDetailMetadata(record),
|
||||
sections: [
|
||||
SuperportDetailDialogSection(
|
||||
id: _OutboundDetailSections.lines,
|
||||
label: '라인 품목',
|
||||
icon: lucide.LucideIcons.listChecks,
|
||||
builder: (_) => OutboundDetailView(
|
||||
record: record,
|
||||
currencyFormatter: _currencyFormatter,
|
||||
transitionsEnabled: _transitionsEnabled,
|
||||
),
|
||||
),
|
||||
],
|
||||
initialSectionId: _OutboundDetailSections.lines,
|
||||
actions: _buildDetailActions(record),
|
||||
constraints: const BoxConstraints(maxWidth: 920),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOutboundDetailSummary(OutboundRecord record) {
|
||||
final theme = ShadTheme.of(context);
|
||||
final processedAt = _dateFormatter.format(record.processedAt);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(record.transactionNumber, style: theme.textTheme.h4),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'$processedAt · ${record.transactionType}',
|
||||
style: theme.textTheme.muted,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildOutboundDetailBadges(OutboundRecord record) {
|
||||
return [
|
||||
ShadBadge(child: Text(record.status)),
|
||||
ShadBadge.outline(child: Text('고객 ${record.customerCount}곳')),
|
||||
];
|
||||
}
|
||||
|
||||
List<SuperportDetailMetadata> _buildOutboundDetailMetadata(
|
||||
OutboundRecord record,
|
||||
) {
|
||||
return [
|
||||
SuperportDetailMetadata.text(
|
||||
label: '트랜잭션번호',
|
||||
value: record.transactionNumber,
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '처리일자',
|
||||
value: _dateFormatter.format(record.processedAt),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '트랜잭션 유형',
|
||||
value: record.transactionType,
|
||||
),
|
||||
SuperportDetailMetadata.text(label: '상태', value: record.status),
|
||||
SuperportDetailMetadata.text(label: '작성자', value: record.writer),
|
||||
SuperportDetailMetadata.text(label: '창고', value: record.warehouse),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '창고 코드',
|
||||
value: _dashIfEmpty(record.warehouseCode),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '창고 우편번호',
|
||||
value: _dashIfEmpty(record.warehouseZipcode),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '창고 주소',
|
||||
value: _dashIfEmpty(record.warehouseAddress),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '고객 수',
|
||||
value: '${record.customerCount}',
|
||||
),
|
||||
SuperportDetailMetadata.text(label: '품목 수', value: '${record.itemCount}'),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '총 수량',
|
||||
value: '${record.totalQuantity}',
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '총 금액',
|
||||
value: _currencyFormatter.format(record.totalAmount),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '비고',
|
||||
value: _dashIfEmpty(record.remark),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
String _dashIfEmpty(String? value) {
|
||||
if (value == null) {
|
||||
return '-';
|
||||
}
|
||||
final trimmed = value.trim();
|
||||
return trimmed.isEmpty ? '-' : trimmed;
|
||||
}
|
||||
|
||||
List<Widget> _buildDetailActions(OutboundRecord record) {
|
||||
final isProcessing = _isProcessing(record.id) || _isLoading;
|
||||
final actions = <Widget>[];
|
||||
|
||||
@@ -9,13 +9,11 @@ class OutboundDetailView extends StatelessWidget {
|
||||
const OutboundDetailView({
|
||||
super.key,
|
||||
required this.record,
|
||||
required this.dateFormatter,
|
||||
required this.currencyFormatter,
|
||||
this.transitionsEnabled = true,
|
||||
});
|
||||
|
||||
final OutboundRecord record;
|
||||
final intl.DateFormat dateFormatter;
|
||||
final intl.NumberFormat currencyFormatter;
|
||||
final bool transitionsEnabled;
|
||||
|
||||
@@ -31,39 +29,6 @@ class OutboundDetailView extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
_DetailChip(
|
||||
label: '처리일자',
|
||||
value: dateFormatter.format(record.processedAt),
|
||||
),
|
||||
_DetailChip(label: '창고', value: record.warehouse),
|
||||
if (record.warehouseCode != null &&
|
||||
record.warehouseCode!.trim().isNotEmpty)
|
||||
_DetailChip(label: '창고 코드', value: record.warehouseCode!),
|
||||
if (record.warehouseZipcode != null &&
|
||||
record.warehouseZipcode!.trim().isNotEmpty)
|
||||
_DetailChip(label: '창고 우편번호', value: record.warehouseZipcode!),
|
||||
if (record.warehouseAddress != null &&
|
||||
record.warehouseAddress!.trim().isNotEmpty)
|
||||
_DetailChip(label: '창고 주소', value: record.warehouseAddress!),
|
||||
_DetailChip(label: '트랜잭션 유형', value: record.transactionType),
|
||||
_DetailChip(label: '상태', value: record.status),
|
||||
_DetailChip(label: '작성자', value: record.writer),
|
||||
_DetailChip(label: '고객 수', value: '${record.customerCount}'),
|
||||
_DetailChip(label: '품목 수', value: '${record.itemCount}'),
|
||||
_DetailChip(label: '총 수량', value: '${record.totalQuantity}'),
|
||||
_DetailChip(
|
||||
label: '총 금액',
|
||||
value: currencyFormatter.format(record.totalAmount),
|
||||
),
|
||||
if (record.remark.isNotEmpty && record.remark != '-')
|
||||
_DetailChip(label: '비고', value: record.remark),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text('출고 고객사', style: theme.textTheme.h4),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
@@ -122,36 +87,3 @@ class OutboundDetailView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DetailChip extends StatelessWidget {
|
||||
const _DetailChip({required this.label, required this.value});
|
||||
|
||||
final String label;
|
||||
final String value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.card,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: theme.colorScheme.border),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: theme.textTheme.small,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(value, style: theme.textTheme.p, textAlign: TextAlign.center),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,10 @@ import '../widgets/rental_detail_view.dart';
|
||||
const String _rentalTransactionTypeRent = '대여';
|
||||
const String _rentalTransactionTypeReturn = '반납';
|
||||
|
||||
class _RentalDetailSections {
|
||||
static const lines = 'lines';
|
||||
}
|
||||
|
||||
/// 대여/반납 목록과 등록 모달을 관리하는 페이지.
|
||||
class RentalPage extends StatefulWidget {
|
||||
const RentalPage({super.key, required this.routeUri});
|
||||
@@ -597,7 +601,11 @@ class _RentalPageState extends State<RentalPage> {
|
||||
RentalTableSpec.rowSpanHeight,
|
||||
),
|
||||
onRowTap: (rowIndex) {
|
||||
final record = visibleRecords[rowIndex];
|
||||
if (rowIndex <= 0 ||
|
||||
rowIndex > visibleRecords.length) {
|
||||
return;
|
||||
}
|
||||
final record = visibleRecords[rowIndex - 1];
|
||||
_selectRecord(record, openDetail: true);
|
||||
},
|
||||
);
|
||||
@@ -829,18 +837,116 @@ class _RentalPageState extends State<RentalPage> {
|
||||
await showInventoryTransactionDetailDialog<void>(
|
||||
context: context,
|
||||
title: '대여 상세',
|
||||
transactionNumber: record.transactionNumber,
|
||||
body: RentalDetailView(
|
||||
record: record,
|
||||
dateFormatter: _dateFormatter,
|
||||
currencyFormatter: _currencyFormatter,
|
||||
transitionsEnabled: _transitionsEnabled,
|
||||
),
|
||||
description: '대여 고객사와 라인 품목을 확인하세요.',
|
||||
summary: _buildRentalDetailSummary(record),
|
||||
summaryBadges: _buildRentalDetailBadges(record),
|
||||
metadata: _buildRentalDetailMetadata(record),
|
||||
sections: [
|
||||
SuperportDetailDialogSection(
|
||||
id: _RentalDetailSections.lines,
|
||||
label: '라인 품목',
|
||||
icon: lucide.LucideIcons.listChecks,
|
||||
builder: (_) => RentalDetailView(
|
||||
record: record,
|
||||
currencyFormatter: _currencyFormatter,
|
||||
transitionsEnabled: _transitionsEnabled,
|
||||
),
|
||||
),
|
||||
],
|
||||
initialSectionId: _RentalDetailSections.lines,
|
||||
actions: _buildDetailActions(record),
|
||||
constraints: const BoxConstraints(maxWidth: 920),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRentalDetailSummary(RentalRecord record) {
|
||||
final theme = ShadTheme.of(context);
|
||||
final processedAt = _dateFormatter.format(record.processedAt);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(record.transactionNumber, style: theme.textTheme.h4),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'$processedAt · ${record.rentalType}',
|
||||
style: theme.textTheme.muted,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildRentalDetailBadges(RentalRecord record) {
|
||||
return [
|
||||
ShadBadge(child: Text(record.status)),
|
||||
ShadBadge.outline(child: Text(record.transactionType)),
|
||||
];
|
||||
}
|
||||
|
||||
List<SuperportDetailMetadata> _buildRentalDetailMetadata(
|
||||
RentalRecord record,
|
||||
) {
|
||||
return [
|
||||
SuperportDetailMetadata.text(
|
||||
label: '트랜잭션번호',
|
||||
value: record.transactionNumber,
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '처리일자',
|
||||
value: _dateFormatter.format(record.processedAt),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '트랜잭션 유형',
|
||||
value: record.transactionType,
|
||||
),
|
||||
SuperportDetailMetadata.text(label: '대여 구분', value: record.rentalType),
|
||||
SuperportDetailMetadata.text(label: '상태', value: record.status),
|
||||
SuperportDetailMetadata.text(label: '작성자', value: record.writer),
|
||||
SuperportDetailMetadata.text(label: '창고', value: record.warehouse),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '창고 코드',
|
||||
value: _dashIfEmpty(record.warehouseCode),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '창고 우편번호',
|
||||
value: _dashIfEmpty(record.warehouseZipcode),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '창고 주소',
|
||||
value: _dashIfEmpty(record.warehouseAddress),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '고객 수',
|
||||
value: '${record.customerCount}',
|
||||
),
|
||||
SuperportDetailMetadata.text(label: '품목 수', value: '${record.itemCount}'),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '총 수량',
|
||||
value: '${record.totalQuantity}',
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '총 금액',
|
||||
value: _currencyFormatter.format(record.totalAmount),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '반납 예정일',
|
||||
value: record.returnDueDate == null
|
||||
? '-'
|
||||
: _dateFormatter.format(record.returnDueDate!),
|
||||
),
|
||||
SuperportDetailMetadata.text(
|
||||
label: '비고',
|
||||
value: _dashIfEmpty(record.remark),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
String _dashIfEmpty(String? value) {
|
||||
if (value == null) {
|
||||
return '-';
|
||||
}
|
||||
final trimmed = value.trim();
|
||||
return trimmed.isEmpty ? '-' : trimmed;
|
||||
}
|
||||
|
||||
List<Widget> _buildDetailActions(RentalRecord record) {
|
||||
final isProcessing = _isProcessing(record.id) || _isLoading;
|
||||
final actions = <Widget>[];
|
||||
|
||||
@@ -9,13 +9,11 @@ class RentalDetailView extends StatelessWidget {
|
||||
const RentalDetailView({
|
||||
super.key,
|
||||
required this.record,
|
||||
required this.dateFormatter,
|
||||
required this.currencyFormatter,
|
||||
this.transitionsEnabled = true,
|
||||
});
|
||||
|
||||
final RentalRecord record;
|
||||
final intl.DateFormat dateFormatter;
|
||||
final intl.NumberFormat currencyFormatter;
|
||||
final bool transitionsEnabled;
|
||||
|
||||
@@ -31,36 +29,6 @@ class RentalDetailView extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
_DetailChip(
|
||||
label: '처리일자',
|
||||
value: dateFormatter.format(record.processedAt),
|
||||
),
|
||||
_DetailChip(label: '창고', value: record.warehouse),
|
||||
_DetailChip(label: '트랜잭션 유형', value: record.transactionType),
|
||||
_DetailChip(label: '대여 구분', value: record.rentalType),
|
||||
_DetailChip(label: '상태', value: record.status),
|
||||
_DetailChip(label: '작성자', value: record.writer),
|
||||
_DetailChip(
|
||||
label: '반납 예정일',
|
||||
value: record.returnDueDate == null
|
||||
? '-'
|
||||
: dateFormatter.format(record.returnDueDate!),
|
||||
),
|
||||
_DetailChip(label: '고객 수', value: '${record.customerCount}'),
|
||||
_DetailChip(label: '총 수량', value: '${record.totalQuantity}'),
|
||||
_DetailChip(
|
||||
label: '총 금액',
|
||||
value: currencyFormatter.format(record.totalAmount),
|
||||
),
|
||||
if (record.remark.isNotEmpty && record.remark != '-')
|
||||
_DetailChip(label: '비고', value: record.remark),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text('연결 고객사', style: theme.textTheme.h4),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
@@ -119,36 +87,3 @@ class RentalDetailView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DetailChip extends StatelessWidget {
|
||||
const _DetailChip({required this.label, required this.value});
|
||||
|
||||
final String label;
|
||||
final String value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.card,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: theme.colorScheme.border),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: theme.textTheme.small,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(value, style: theme.textTheme.p, textAlign: TextAlign.center),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,47 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
import '../../../../../widgets/components/superport_dialog.dart';
|
||||
import '../../../../../widgets/components/superport_detail_dialog.dart';
|
||||
|
||||
/// 재고 트랜잭션 상세 정보를 Superport 다이얼로그로 표시하는 헬퍼이다.
|
||||
export '../../../../../widgets/components/superport_detail_dialog.dart'
|
||||
show SuperportDetailDialogSection, SuperportDetailMetadata;
|
||||
|
||||
/// 재고 트랜잭션 상세 정보를 Superport 상세 다이얼로그로 감싼다.
|
||||
Future<T?> showInventoryTransactionDetailDialog<T>({
|
||||
required BuildContext context,
|
||||
required String title,
|
||||
required String transactionNumber,
|
||||
required Widget body,
|
||||
required List<SuperportDetailMetadata> metadata,
|
||||
List<SuperportDetailDialogSection> sections = const [],
|
||||
Widget? summary,
|
||||
List<Widget> summaryBadges = const [],
|
||||
String? description,
|
||||
List<Widget> actions = const [],
|
||||
bool includeDefaultClose = true,
|
||||
bool barrierDismissible = true,
|
||||
BoxConstraints? constraints,
|
||||
EdgeInsetsGeometry? contentPadding,
|
||||
bool scrollable = true,
|
||||
bool barrierDismissible = true,
|
||||
int metadataColumns = 2,
|
||||
String? initialSectionId,
|
||||
int initialSectionIndex = 0,
|
||||
FutureOr<void> Function()? onSubmit,
|
||||
}) {
|
||||
final resolvedActions = <Widget>[
|
||||
...actions,
|
||||
if (includeDefaultClose)
|
||||
ShadButton.ghost(
|
||||
onPressed: () => Navigator.of(context, rootNavigator: true).maybePop(),
|
||||
child: const Text('닫기'),
|
||||
),
|
||||
];
|
||||
|
||||
return showSuperportDialog<T>(
|
||||
return showSuperportDetailDialog<T>(
|
||||
context: context,
|
||||
title: title,
|
||||
description: '트랜잭션번호 $transactionNumber',
|
||||
body: body,
|
||||
actions: resolvedActions,
|
||||
constraints: constraints,
|
||||
contentPadding: contentPadding,
|
||||
scrollable: scrollable,
|
||||
description: description,
|
||||
summary: summary,
|
||||
summaryBadges: summaryBadges,
|
||||
metadata: metadata,
|
||||
sections: sections,
|
||||
actions: actions,
|
||||
barrierDismissible: barrierDismissible,
|
||||
constraints: constraints ?? const BoxConstraints(maxWidth: 920),
|
||||
contentPadding:
|
||||
contentPadding ??
|
||||
const EdgeInsets.symmetric(horizontal: 24, vertical: 24),
|
||||
metadataColumns: metadataColumns,
|
||||
initialSectionId: initialSectionId,
|
||||
initialSectionIndex: initialSectionIndex,
|
||||
onSubmit: onSubmit,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user