재고 상세 다이얼로그화 및 마스터 레이아웃 개선

This commit is contained in:
JiWoong Sul
2025-10-22 18:52:21 +09:00
parent a14133df52
commit 09c31b2503
20 changed files with 1187 additions and 923 deletions

View File

@@ -0,0 +1,157 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart' as intl;
import 'package:shadcn_ui/shadcn_ui.dart';
import '../models/rental_record.dart';
/// 대여 트랜잭션 상세 정보를 다이얼로그로 노출하는 뷰이다.
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;
@override
Widget build(BuildContext context) {
final theme = ShadTheme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!transitionsEnabled) ...[
ShadBadge.outline(
child: Text(
'재고 상태 전이가 비활성화된 상태입니다.',
style: theme.textTheme.small,
),
),
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(
spacing: 8,
runSpacing: 8,
children: [
for (final customer in record.customers)
ShadBadge(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
child: Text('${customer.name} · ${customer.code}'),
),
),
],
),
const SizedBox(height: 24),
Text('라인 품목', style: theme.textTheme.h4),
const SizedBox(height: 8),
_buildLineTable(),
],
);
}
Widget _buildLineTable() {
final height = (record.items.length * 52).clamp(160, 260).toDouble();
return SizedBox(
height: height,
child: ShadTable.list(
header: const [
ShadTableCell.header(child: Text('제품')),
ShadTableCell.header(child: Text('제조사')),
ShadTableCell.header(child: Text('단위')),
ShadTableCell.header(child: Text('수량')),
ShadTableCell.header(child: Text('단가')),
ShadTableCell.header(child: Text('비고')),
],
children: [
for (final item in record.items)
[
ShadTableCell(child: Text(item.product)),
ShadTableCell(child: Text(item.manufacturer)),
ShadTableCell(child: Text(item.unit)),
ShadTableCell(child: Text('${item.quantity}')),
ShadTableCell(
child: Text(currencyFormatter.format(item.price)),
),
ShadTableCell(
child: Text(item.remark.isEmpty ? '-' : item.remark),
),
],
],
columnSpanExtent: (index) => const FixedTableSpanExtent(136),
rowSpanExtent: (index) => const FixedTableSpanExtent(52),
),
);
}
}
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,
),
],
),
);
}
}