사용하지 않는 파일 정리 전 백업 (Phase 10 완료 후 상태)

This commit is contained in:
JiWoong Sul
2025-08-29 15:11:59 +09:00
parent a740ff10c8
commit d916b281a7
333 changed files with 53617 additions and 22574 deletions

View File

@@ -0,0 +1,188 @@
import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:superport/data/models/model_dto.dart';
import 'package:superport/data/models/vendor_dto.dart';
import 'package:superport/screens/model/controllers/model_controller.dart';
/// Vendor별로 그룹화된 Model 테이블
class ModelGroupedTable extends StatelessWidget {
final ModelController controller;
final Function(ModelDto) onEdit;
final Function(ModelDto) onDelete;
const ModelGroupedTable({
super.key,
required this.controller,
required this.onEdit,
required this.onDelete,
});
@override
Widget build(BuildContext context) {
final modelsByVendor = controller.modelsByVendor;
if (modelsByVendor.isEmpty) {
return const Center(
child: Text('등록된 모델이 없습니다.'),
);
}
return ListView.builder(
itemCount: modelsByVendor.length,
itemBuilder: (context, index) {
final vendorId = modelsByVendor.keys.elementAt(index);
final vendor = controller.getVendorById(vendorId);
final models = modelsByVendor[vendorId] ?? [];
return _buildVendorGroup(context, vendor, models);
},
);
}
Widget _buildVendorGroup(
BuildContext context,
VendorDto? vendor,
List<ModelDto> models,
) {
return ShadCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Vendor 헤더
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(8),
topRight: Radius.circular(8),
),
),
child: Row(
children: [
Icon(
Icons.business,
size: 20,
color: Colors.grey.shade700,
),
const SizedBox(width: 8),
Expanded(
child: Text(
vendor?.name ?? 'Unknown Vendor',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
ShadBadge(
child: Text('${models.length}개 모델'),
),
],
),
),
// Model 목록
...models.map((model) => _buildModelRow(context, model)),
],
),
);
}
Widget _buildModelRow(BuildContext context, ModelDto model) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey.shade200,
width: 1,
),
),
),
child: Row(
children: [
// Model ID
SizedBox(
width: 60,
child: Text(
'#${model.id}',
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 12,
),
),
),
// Model Name
Expanded(
flex: 2,
child: Text(
model.name,
style: const TextStyle(
fontWeight: FontWeight.w500,
),
),
),
// Description
Expanded(
flex: 3,
child: Text(
'-', // 백엔드에 description 필드 없음
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 14,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
// Status
SizedBox(
width: 80,
child: ShadBadge(
backgroundColor: model.isActive
? Colors.green.shade100
: Colors.grey.shade200,
child: Text(
model.isActive ? '활성' : '비활성',
style: TextStyle(
fontSize: 12,
color: model.isActive ? Colors.green.shade700 : Colors.grey.shade700,
),
),
),
),
// Actions
SizedBox(
width: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: const Icon(Icons.edit, size: 18),
onPressed: () => onEdit(model),
tooltip: '수정',
padding: const EdgeInsets.all(4),
constraints: const BoxConstraints(),
),
const SizedBox(width: 8),
IconButton(
icon: const Icon(Icons.delete, size: 18),
onPressed: () => onDelete(model),
tooltip: '삭제',
padding: const EdgeInsets.all(4),
constraints: const BoxConstraints(),
color: Colors.red,
),
],
),
),
],
),
);
}
}

View File

@@ -0,0 +1,226 @@
import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:superport/data/models/model_dto.dart';
import 'package:superport/data/models/vendor_dto.dart';
import 'package:superport/screens/model/controllers/model_controller.dart';
/// Vendor → Model 캐스케이드 선택 컴포넌트
/// Equipment 등의 화면에서 재사용 가능
class ModelVendorCascade extends StatefulWidget {
final ModelController controller;
final int? initialVendorId;
final int? initialModelId;
final void Function(int? vendorId, int? modelId)? onChanged;
final bool isRequired;
const ModelVendorCascade({
super.key,
required this.controller,
this.initialVendorId,
this.initialModelId,
this.onChanged,
this.isRequired = true,
});
@override
State<ModelVendorCascade> createState() => _ModelVendorCascadeState();
}
class _ModelVendorCascadeState extends State<ModelVendorCascade> {
int? _selectedVendorId;
int? _selectedModelId;
List<ModelDto> _availableModels = [];
bool _isLoadingModels = false;
@override
void initState() {
super.initState();
_selectedVendorId = widget.initialVendorId;
_selectedModelId = widget.initialModelId;
// 초기 vendor가 있으면 모델 로드
if (_selectedVendorId != null) {
_loadModelsForVendor(_selectedVendorId!);
}
}
Future<void> _loadModelsForVendor(int vendorId) async {
setState(() {
_isLoadingModels = true;
});
try {
// Controller에서 해당 vendor의 모델 가져오기
final models = widget.controller.getModelsByVendor(vendorId);
setState(() {
_availableModels = models;
// 선택된 모델이 새 vendor의 모델 목록에 없으면 초기화
if (_selectedModelId != null &&
!models.any((m) => m.id == _selectedModelId)) {
_selectedModelId = null;
}
});
} finally {
setState(() {
_isLoadingModels = false;
});
}
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Vendor 선택
_buildVendorSelect(),
const SizedBox(height: 16),
// Model 선택 (Vendor가 선택된 경우에만 표시)
if (_selectedVendorId != null) _buildModelSelect(),
],
);
}
Widget _buildVendorSelect() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'제조사',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
if (widget.isRequired)
const Text(
' *',
style: TextStyle(color: Colors.red),
),
],
),
const SizedBox(height: 8),
ShadSelect<int?>(
placeholder: const Text('제조사를 선택하세요'),
options: [
if (!widget.isRequired)
const ShadOption(
value: null,
child: Text('선택 안함'),
),
...widget.controller.vendors.map(
(vendor) => ShadOption(
value: vendor.id,
child: Text(vendor.name),
),
),
],
selectedOptionBuilder: (context, value) {
if (value == null) {
return const Text('선택 안함');
}
final vendor = widget.controller.vendors.firstWhere(
(v) => v.id == value,
orElse: () => const VendorDto(
name: 'Unknown',
),
);
return Text(vendor.name);
},
onChanged: (value) {
setState(() {
_selectedVendorId = value;
_selectedModelId = null; // Vendor 변경 시 Model 초기화
_availableModels.clear();
});
if (value != null) {
_loadModelsForVendor(value);
}
// 콜백 호출
widget.onChanged?.call(value, null);
},
initialValue: _selectedVendorId,
),
],
);
}
Widget _buildModelSelect() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'모델',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
if (widget.isRequired)
const Text(
' *',
style: TextStyle(color: Colors.red),
),
],
),
const SizedBox(height: 8),
if (_isLoadingModels)
const ShadInput(
placeholder: Text('모델 로딩 중...'),
enabled: false,
)
else if (_availableModels.isEmpty)
const ShadInput(
placeholder: Text('해당 제조사에 등록된 모델이 없습니다'),
enabled: false,
)
else
ShadSelect<int?>(
placeholder: const Text('모델을 선택하세요'),
options: [
if (!widget.isRequired)
const ShadOption(
value: null,
child: Text('선택 안함'),
),
..._availableModels.map(
(model) => ShadOption(
value: model.id,
child: Text(model.name),
),
),
],
selectedOptionBuilder: (context, value) {
if (value == null) {
return const Text('선택 안함');
}
final model = _availableModels.firstWhere(
(m) => m.id == value,
orElse: () => ModelDto(
id: value,
vendorsId: _selectedVendorId!,
name: 'Unknown',
),
);
return Text(model.name);
},
onChanged: (value) {
setState(() {
_selectedModelId = value;
});
// 콜백 호출
widget.onChanged?.call(_selectedVendorId, value);
},
initialValue: _selectedModelId,
),
],
);
}
}