web: migrate health notifications to js_interop; add browser hook
- Replace dart:js with package:js in health_check_service_web.dart\n- Implement showHealthCheckNotification in web/index.html\n- Pin js dependency to ^0.6.7 for flutter_secure_storage_web compatibility auth: harden AuthInterceptor + tests - Allow overrideAuthRepository injection for testing\n- Normalize imports to package: paths\n- Add unit test covering token attach, 401→refresh→retry, and failure path\n- Add integration test skeleton gated by env vars ui/data: map User.companyName to list column - Add companyName to domain User\n- Map UserDto.company?.name\n- Render companyName in user_list cleanup: remove legacy equipment table + unused code; minor warnings - Remove _buildFlexibleTable and unused helpers\n- Remove unused zipcode details and cache retry constant\n- Fix null-aware and non-null assertions\n- Address child-last warnings in administrator dialog docs: update AGENTS.md session context
This commit is contained in:
@@ -75,180 +75,8 @@ class _InventoryHistoryScreenState extends State<InventoryHistoryScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 헤더 셀 빌더
|
||||
Widget _buildHeaderCell(
|
||||
String text, {
|
||||
required int flex,
|
||||
required bool useExpanded,
|
||||
required double minWidth,
|
||||
}) {
|
||||
final child = Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12),
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
);
|
||||
// (Deprecated) 기존 커스텀 테이블 빌더 유틸들은 ShadTable 전환으로 제거되었습니다.
|
||||
|
||||
if (useExpanded) {
|
||||
return Expanded(flex: flex, child: child);
|
||||
} else {
|
||||
return SizedBox(width: minWidth, child: child);
|
||||
}
|
||||
}
|
||||
|
||||
/// 데이터 셀 빌더
|
||||
Widget _buildDataCell(
|
||||
Widget child, {
|
||||
required int flex,
|
||||
required bool useExpanded,
|
||||
required double minWidth,
|
||||
}) {
|
||||
final container = Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||
child: child,
|
||||
);
|
||||
|
||||
if (useExpanded) {
|
||||
return Expanded(flex: flex, child: container);
|
||||
} else {
|
||||
return SizedBox(width: minWidth, child: container);
|
||||
}
|
||||
}
|
||||
|
||||
/// 헤더 셀 리스트 (요구사항에 맞게 재정의)
|
||||
List<Widget> _buildHeaderCells() {
|
||||
return [
|
||||
_buildHeaderCell('장비명', flex: 3, useExpanded: true, minWidth: 150),
|
||||
_buildHeaderCell('시리얼번호', flex: 2, useExpanded: true, minWidth: 120),
|
||||
_buildHeaderCell('위치', flex: 2, useExpanded: true, minWidth: 120),
|
||||
_buildHeaderCell('변동일', flex: 1, useExpanded: false, minWidth: 100),
|
||||
_buildHeaderCell('작업', flex: 0, useExpanded: false, minWidth: 80),
|
||||
_buildHeaderCell('비고', flex: 2, useExpanded: true, minWidth: 120),
|
||||
];
|
||||
}
|
||||
|
||||
/// 테이블 행 빌더 (요구사항에 맞게 재정의)
|
||||
Widget _buildTableRow(InventoryHistoryViewModel history, int index) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: index.isEven ? ShadcnTheme.muted.withValues(alpha: 0.1) : null,
|
||||
border: const Border(
|
||||
bottom: BorderSide(color: Colors.black12, width: 1),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// 장비명
|
||||
_buildDataCell(
|
||||
Tooltip(
|
||||
message: history.equipmentName,
|
||||
child: Text(
|
||||
history.equipmentName,
|
||||
style: ShadcnTheme.bodyMedium.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
flex: 3,
|
||||
useExpanded: true,
|
||||
minWidth: 150,
|
||||
),
|
||||
// 시리얼번호
|
||||
_buildDataCell(
|
||||
Tooltip(
|
||||
message: history.serialNumber,
|
||||
child: Text(
|
||||
history.serialNumber,
|
||||
style: ShadcnTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
flex: 2,
|
||||
useExpanded: true,
|
||||
minWidth: 120,
|
||||
),
|
||||
// 위치 (출고/대여: 고객사, 입고/폐기: 창고)
|
||||
_buildDataCell(
|
||||
Tooltip(
|
||||
message: history.location,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
history.isCustomerLocation ? Icons.business : Icons.warehouse,
|
||||
size: 14,
|
||||
color: history.isCustomerLocation ? Colors.blue : Colors.green,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Text(
|
||||
history.location,
|
||||
style: ShadcnTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
flex: 2,
|
||||
useExpanded: true,
|
||||
minWidth: 120,
|
||||
),
|
||||
// 변동일
|
||||
_buildDataCell(
|
||||
Text(
|
||||
history.formattedDate,
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
flex: 1,
|
||||
useExpanded: false,
|
||||
minWidth: 100,
|
||||
),
|
||||
// 작업 (상세보기만)
|
||||
_buildDataCell(
|
||||
ShadButton.outline(
|
||||
size: ShadButtonSize.sm,
|
||||
onPressed: () => _showEquipmentHistoryDetail(history),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.history, size: 14),
|
||||
SizedBox(width: 4),
|
||||
Text('상세보기', style: TextStyle(fontSize: 12)),
|
||||
],
|
||||
),
|
||||
),
|
||||
flex: 0,
|
||||
useExpanded: false,
|
||||
minWidth: 80,
|
||||
),
|
||||
// 비고
|
||||
_buildDataCell(
|
||||
Tooltip(
|
||||
message: history.remark ?? '비고 없음',
|
||||
child: Text(
|
||||
history.remark ?? '-',
|
||||
style: ShadcnTheme.bodySmall.copyWith(
|
||||
color: ShadcnTheme.mutedForeground,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
flex: 2,
|
||||
useExpanded: true,
|
||||
minWidth: 120,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 장비 이력 상세보기 다이얼로그 표시
|
||||
void _showEquipmentHistoryDetail(InventoryHistoryViewModel history) async {
|
||||
@@ -304,17 +132,104 @@ class _InventoryHistoryScreenState extends State<InventoryHistoryScreen> {
|
||||
height: 40,
|
||||
width: 120,
|
||||
child: ShadSelect<String>(
|
||||
selectedOptionBuilder: (context, value) => Text(
|
||||
_getTransactionTypeDisplayText(value),
|
||||
style: const TextStyle(fontSize: 14),
|
||||
selectedOptionBuilder: (context, value) => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (value != 'all') ...[
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: _getTransactionTypeColor(value),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
Text(
|
||||
_getTransactionTypeDisplayText(value),
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
],
|
||||
),
|
||||
placeholder: const Text('거래 유형'),
|
||||
options: [
|
||||
const ShadOption(value: 'all', child: Text('전체')),
|
||||
const ShadOption(value: 'I', child: Text('입고')),
|
||||
const ShadOption(value: 'O', child: Text('출고')),
|
||||
const ShadOption(value: 'R', child: Text('대여')),
|
||||
const ShadOption(value: 'D', child: Text('폐기')),
|
||||
const ShadOption(
|
||||
value: 'all',
|
||||
child: Text('전체'),
|
||||
),
|
||||
ShadOption(
|
||||
value: 'I',
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.equipmentIn,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const Text('입고'),
|
||||
],
|
||||
),
|
||||
),
|
||||
ShadOption(
|
||||
value: 'O',
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.equipmentOut,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const Text('출고'),
|
||||
],
|
||||
),
|
||||
),
|
||||
ShadOption(
|
||||
value: 'R',
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.equipmentRent,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const Text('대여'),
|
||||
],
|
||||
),
|
||||
),
|
||||
ShadOption(
|
||||
value: 'D',
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.equipmentDisposal,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const Text('폐기'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
@@ -407,7 +322,7 @@ class _InventoryHistoryScreenState extends State<InventoryHistoryScreen> {
|
||||
],
|
||||
totalCount: stats['total'],
|
||||
statusMessage: controller.hasActiveFilters
|
||||
? '${controller.filterStatusText}'
|
||||
? controller.filterStatusText
|
||||
: '장비 입출고 이력을 조회합니다',
|
||||
);
|
||||
},
|
||||
@@ -432,7 +347,23 @@ class _InventoryHistoryScreenState extends State<InventoryHistoryScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 데이터 테이블 빌더
|
||||
/// 거래 유형별 Phase 10 색상 반환
|
||||
Color _getTransactionTypeColor(String type) {
|
||||
switch (type) {
|
||||
case 'I':
|
||||
return ShadcnTheme.equipmentIn; // 입고 - 그린
|
||||
case 'O':
|
||||
return ShadcnTheme.equipmentOut; // 출고 - 블루
|
||||
case 'R':
|
||||
return ShadcnTheme.equipmentRent; // 대여 - 퍼플
|
||||
case 'D':
|
||||
return ShadcnTheme.equipmentDisposal; // 폐기 - 그레이
|
||||
default:
|
||||
return ShadcnTheme.foregroundMuted; // 기본/전체
|
||||
}
|
||||
}
|
||||
|
||||
/// 데이터 테이블 빌더 (ShadTable)
|
||||
Widget _buildDataTable(List<InventoryHistoryViewModel> historyList) {
|
||||
if (historyList.isEmpty) {
|
||||
return Center(
|
||||
@@ -471,31 +402,66 @@ class _InventoryHistoryScreenState extends State<InventoryHistoryScreen> {
|
||||
border: Border.all(color: ShadcnTheme.border),
|
||||
borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 고정 헤더
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.muted.withValues(alpha: 0.3),
|
||||
border: const Border(
|
||||
bottom: BorderSide(color: Colors.black12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
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: historyList.map((history) {
|
||||
return [
|
||||
// 장비명
|
||||
ShadTableCell(
|
||||
child: Tooltip(
|
||||
message: history.equipmentName,
|
||||
child: Text(history.equipmentName, overflow: TextOverflow.ellipsis, style: ShadcnTheme.bodyMedium.copyWith(fontWeight: FontWeight.w500)),
|
||||
),
|
||||
),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
topRight: Radius.circular(8),
|
||||
// 시리얼번호
|
||||
ShadTableCell(
|
||||
child: Tooltip(
|
||||
message: history.serialNumber,
|
||||
child: Text(history.serialNumber, overflow: TextOverflow.ellipsis, style: ShadcnTheme.bodySmall),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(children: _buildHeaderCells()),
|
||||
),
|
||||
// 스크롤 바디
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: historyList.length,
|
||||
itemBuilder: (context, index) => _buildTableRow(historyList[index], index),
|
||||
),
|
||||
),
|
||||
],
|
||||
// 위치
|
||||
ShadTableCell(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(history.isCustomerLocation ? Icons.business : Icons.warehouse, size: 14, color: history.isCustomerLocation ? ShadcnTheme.companyCustomer : ShadcnTheme.equipmentIn),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(child: Text(history.location, overflow: TextOverflow.ellipsis, style: ShadcnTheme.bodySmall)),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 변동일
|
||||
ShadTableCell(child: Text(history.formattedDate, style: ShadcnTheme.bodySmall)),
|
||||
// 작업
|
||||
ShadTableCell(
|
||||
child: ShadButton.outline(
|
||||
size: ShadButtonSize.sm,
|
||||
onPressed: () => _showEquipmentHistoryDetail(history),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [Icon(Icons.history, size: 14), SizedBox(width: 4), Text('상세보기', style: TextStyle(fontSize: 12))],
|
||||
),
|
||||
),
|
||||
),
|
||||
// 비고
|
||||
ShadTableCell(
|
||||
child: Tooltip(
|
||||
message: history.remark ?? '비고 없음',
|
||||
child: Text(history.remark ?? '-', overflow: TextOverflow.ellipsis, style: ShadcnTheme.bodySmall.copyWith(color: ShadcnTheme.mutedForeground)),
|
||||
),
|
||||
),
|
||||
];
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -539,4 +505,4 @@ class _InventoryHistoryScreenState extends State<InventoryHistoryScreen> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user