Files
superport/lib/screens/model/model_list_screen.dart
JiWoong Sul c419f8f458 backup: 사용하지 않는 파일 삭제 전 복구 지점
- 전체 371개 파일 중 82개 미사용 파일 식별
- Phase 1: 33개 파일 삭제 예정 (100% 안전)
- Phase 2: 30개 파일 삭제 검토 예정
- Phase 3: 19개 파일 수동 검토 예정

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-02 19:51:40 +09:00

470 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:superport/data/models/model/model_dto.dart';
import 'package:superport/screens/model/controllers/model_controller.dart';
import 'package:superport/screens/model/model_form_dialog.dart';
import 'package:superport/screens/common/layouts/base_list_screen.dart';
import 'package:superport/screens/common/widgets/standard_action_bar.dart';
import 'package:superport/screens/common/theme_shadcn.dart';
import 'package:superport/injection_container.dart' as di;
class ModelListScreen extends StatefulWidget {
const ModelListScreen({super.key});
@override
State<ModelListScreen> createState() => _ModelListScreenState();
}
class _ModelListScreenState extends State<ModelListScreen> {
late final ModelController _controller;
@override
void initState() {
super.initState();
_controller = di.sl<ModelController>();
WidgetsBinding.instance.addPostFrameCallback((_) {
_controller.loadInitialData();
});
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: _controller,
child: Scaffold(
backgroundColor: ShadcnTheme.background,
appBar: AppBar(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'모델 관리',
style: ShadcnTheme.headingH4,
),
Text(
'장비 모델 정보를 관리합니다',
style: ShadcnTheme.bodySmall,
),
],
),
backgroundColor: ShadcnTheme.background,
elevation: 0,
),
body: Consumer<ModelController>(
builder: (context, controller, child) {
return BaseListScreen(
headerSection: _buildStatisticsCards(controller),
searchBar: _buildSearchBar(controller),
actionBar: _buildActionBar(),
dataTable: _buildDataTable(controller),
pagination: _buildPagination(controller),
isLoading: controller.isLoading,
error: controller.errorMessage,
onRefresh: () => controller.loadInitialData(),
);
},
),
),
);
}
Widget _buildStatisticsCards(ModelController controller) {
if (controller.isLoading) return const SizedBox();
return Row(
children: [
_buildStatCard(
'전체 모델',
controller.models.length.toString(),
Icons.category,
ShadcnTheme.primary,
),
const SizedBox(width: ShadcnTheme.spacing4),
_buildStatCard(
'제조사',
controller.vendors.length.toString(),
Icons.business,
ShadcnTheme.success,
),
const SizedBox(width: ShadcnTheme.spacing4),
_buildStatCard(
'활성 모델',
controller.models.where((m) => !m.isDeleted).length.toString(),
Icons.check_circle,
ShadcnTheme.info,
),
],
);
}
Widget _buildSearchBar(ModelController controller) {
return Row(
children: [
Expanded(
flex: 2,
child: ShadInput(
placeholder: const Text('모델명 검색...'),
onChanged: controller.setSearchQuery,
),
),
const SizedBox(width: ShadcnTheme.spacing4),
Expanded(
child: ShadSelect<int?>(
placeholder: const Text('제조사 선택'),
options: [
const ShadOption<int?>(
value: null,
child: Text('전체'),
),
...controller.vendors.map(
(vendor) => ShadOption<int?>(
value: vendor.id,
child: Text(vendor.name),
),
),
],
selectedOptionBuilder: (context, value) {
if (value == null) {
return const Text('전체');
}
try {
final vendor = controller.vendors.firstWhere((v) => v.id == value);
return Text(vendor.name);
} catch (_) {
return const Text('전체');
}
},
onChanged: controller.setVendorFilter,
),
),
],
);
}
Widget _buildActionBar() {
return StandardActionBar(
totalCount: _controller.totalCount,
leftActions: const [
Text('모델 목록', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
],
rightActions: [
ShadButton.outline(
onPressed: () => _controller.refreshModels(),
child: const Icon(Icons.refresh, size: 16),
),
const SizedBox(width: ShadcnTheme.spacing2),
ShadButton(
onPressed: _showCreateDialog,
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.add, size: 16),
SizedBox(width: ShadcnTheme.spacing1),
Text('새 모델 등록'),
],
),
),
],
);
}
/// 헤더 셀 빌더
Widget _buildHeaderCell(
String text, {
required int flex,
required bool useExpanded,
required double minWidth,
}) {
final child = Container(
alignment: Alignment.centerLeft,
child: Text(
text,
style: ShadcnTheme.bodyMedium.copyWith(fontWeight: FontWeight.w500),
),
);
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,
child: child,
);
if (useExpanded) {
return Expanded(flex: flex, child: container);
} else {
return SizedBox(width: minWidth, child: container);
}
}
/// 헤더 셀 리스트
List<Widget> _buildHeaderCells() {
return [
_buildHeaderCell('ID', flex: 0, useExpanded: false, minWidth: 60),
_buildHeaderCell('제조사', flex: 2, useExpanded: true, minWidth: 100),
_buildHeaderCell('모델명', flex: 3, useExpanded: true, minWidth: 120),
_buildHeaderCell('등록일', flex: 2, useExpanded: true, minWidth: 100),
_buildHeaderCell('상태', flex: 0, useExpanded: false, minWidth: 80),
_buildHeaderCell('작업', flex: 0, useExpanded: false, minWidth: 100),
];
}
/// 테이블 행 빌더
Widget _buildTableRow(ModelDto model, int index) {
final vendor = _controller.getVendorById(model.vendorsId);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
decoration: BoxDecoration(
color: index.isEven
? ShadcnTheme.muted.withValues(alpha: 0.1)
: null,
border: const Border(
bottom: BorderSide(color: Colors.black),
),
),
child: Row(
children: [
_buildDataCell(
Text(
model.id.toString(),
style: ShadcnTheme.bodySmall,
),
flex: 0,
useExpanded: false,
minWidth: 60,
),
_buildDataCell(
Text(
vendor?.name ?? '알 수 없음',
style: ShadcnTheme.bodyMedium,
),
flex: 2,
useExpanded: true,
minWidth: 100,
),
_buildDataCell(
Text(
model.name,
style: ShadcnTheme.bodyMedium.copyWith(
fontWeight: FontWeight.w500,
),
),
flex: 3,
useExpanded: true,
minWidth: 120,
),
_buildDataCell(
Text(
model.registeredAt != null
? DateFormat('yyyy-MM-dd').format(model.registeredAt)
: '-',
style: ShadcnTheme.bodySmall,
),
flex: 2,
useExpanded: true,
minWidth: 100,
),
_buildDataCell(
_buildStatusChip(model.isDeleted),
flex: 0,
useExpanded: false,
minWidth: 80,
),
_buildDataCell(
Row(
mainAxisSize: MainAxisSize.min,
children: [
ShadButton.ghost(
onPressed: () => _showEditDialog(model),
child: const Icon(Icons.edit, size: 16),
),
const SizedBox(width: ShadcnTheme.spacing1),
ShadButton.ghost(
onPressed: () => _showDeleteConfirmDialog(model),
child: const Icon(Icons.delete, size: 16),
),
],
),
flex: 0,
useExpanded: false,
minWidth: 100,
),
],
),
);
}
Widget _buildDataTable(ModelController controller) {
final models = controller.models;
return Container(
width: double.infinity,
decoration: BoxDecoration(
border: Border.all(color: Colors.black),
borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd),
),
child: Column(
children: [
// 고정 헤더
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: ShadcnTheme.muted.withValues(alpha: 0.3),
border: Border(bottom: BorderSide(color: Colors.black)),
),
child: Row(children: _buildHeaderCells()),
),
// 스크롤 바디
Expanded(
child: models.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.category_outlined,
size: 64,
color: ShadcnTheme.mutedForeground,
),
const SizedBox(height: 16),
Text(
'등록된 모델이 없습니다',
style: ShadcnTheme.bodyMedium.copyWith(
color: ShadcnTheme.mutedForeground,
),
),
],
),
)
: ListView.builder(
itemCount: models.length,
itemBuilder: (context, index) => _buildTableRow(models[index], index),
),
),
],
),
);
}
Widget _buildPagination(ModelController controller) {
// 모델 목록은 현재 페이지네이션이 없는 것 같으니 빈 위젯 반환
return const SizedBox();
}
Widget _buildStatCard(
String title,
String value,
IconData icon,
Color color,
) {
return Expanded(
child: ShadCard(
child: Padding(
padding: const EdgeInsets.all(ShadcnTheme.spacing4),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(ShadcnTheme.spacing3),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(ShadcnTheme.radiusLg),
),
child: Icon(
icon,
color: color,
size: 20,
),
),
const SizedBox(width: ShadcnTheme.spacing3),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: ShadcnTheme.bodySmall,
),
Text(
value,
style: ShadcnTheme.headingH6.copyWith(
color: color,
),
),
],
),
),
],
),
),
),
);
}
Widget _buildStatusChip(bool isDeleted) {
if (isDeleted) {
return ShadBadge.destructive(
child: const Text('비활성'),
);
} else {
return ShadBadge.secondary(
child: const Text('활성'),
);
}
}
void _showCreateDialog() {
showDialog(
context: context,
builder: (context) => ModelFormDialog(
controller: _controller,
),
);
}
void _showEditDialog(ModelDto model) {
showDialog(
context: context,
builder: (context) => ModelFormDialog(
controller: _controller,
model: model,
),
);
}
void _showDeleteConfirmDialog(ModelDto model) {
showDialog(
context: context,
builder: (context) => ShadDialog(
title: const Text('모델 삭제'),
description: Text('${model.name} 모델을 삭제하시겠습니까?'),
actions: [
ShadButton.outline(
onPressed: () => Navigator.of(context).pop(),
child: const Text('취소'),
),
ShadButton.destructive(
onPressed: () async {
Navigator.of(context).pop();
await _controller.deleteModel(model.id);
},
child: const Text('삭제'),
),
],
),
);
}
}