사용하지 않는 파일 정리 전 백업 (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,
),
],
);
}
}

View File

@@ -0,0 +1,253 @@
import 'package:flutter/material.dart';
import 'package:injectable/injectable.dart';
import 'package:superport/data/models/model_dto.dart';
import 'package:superport/data/models/vendor_dto.dart';
import 'package:superport/domain/usecases/model_usecase.dart';
import 'package:superport/domain/usecases/vendor_usecase.dart';
/// Model 관리 화면의 상태 관리 Controller
@lazySingleton
class ModelController extends ChangeNotifier {
final ModelUseCase _modelUseCase;
final VendorUseCase _vendorUseCase;
ModelController(this._modelUseCase, this._vendorUseCase);
// 상태 변수들
List<ModelDto> _models = [];
List<ModelDto> _filteredModels = [];
List<VendorDto> _vendors = [];
final Map<int, List<ModelDto>> _modelsByVendor = {};
bool _isLoading = false;
bool _isSubmitting = false;
String? _errorMessage;
String _searchQuery = '';
int? _selectedVendorId;
// Getters
List<ModelDto> get models => _filteredModels;
List<ModelDto> get allModels => _models;
List<VendorDto> get vendors => _vendors;
Map<int, List<ModelDto>> get modelsByVendor => _modelsByVendor;
bool get isLoading => _isLoading;
bool get isSubmitting => _isSubmitting;
String? get errorMessage => _errorMessage;
String get searchQuery => _searchQuery;
int? get selectedVendorId => _selectedVendorId;
/// 초기 데이터 로드
Future<void> loadInitialData() async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
// Vendor와 Model 데이터 병렬 로드
final results = await Future.wait([
_vendorUseCase.getVendors(),
_modelUseCase.getModels(),
]);
_vendors = List<VendorDto>.from(results[0] as List<VendorDto>);
_models = List<ModelDto>.from(results[1] as List<ModelDto>);
_filteredModels = List.from(_models);
// Vendor별로 모델 그룹핑
await _groupModelsByVendor();
} catch (e) {
_errorMessage = e.toString();
} finally {
_isLoading = false;
notifyListeners();
}
}
/// 모델 목록 새로고침
Future<void> refreshModels() async {
_errorMessage = null;
try {
_models = List.from(await _modelUseCase.getModels(vendorId: _selectedVendorId));
_applyFilters();
await _groupModelsByVendor();
} catch (e) {
_errorMessage = e.toString();
} finally {
notifyListeners();
}
}
/// 모델 생성
Future<bool> createModel({
required int vendorsId,
required String name,
}) async {
_isSubmitting = true;
_errorMessage = null;
notifyListeners();
try {
final newModel = await _modelUseCase.createModel(
vendorsId: vendorsId,
name: name,
);
// 목록에 추가
_models = [..._models, newModel];
_applyFilters();
await _groupModelsByVendor();
return true;
} catch (e) {
_errorMessage = e.toString();
return false;
} finally {
_isSubmitting = false;
notifyListeners();
}
}
/// 모델 수정
Future<bool> updateModel({
required int id,
required int vendorsId,
required String name,
}) async {
_isSubmitting = true;
_errorMessage = null;
notifyListeners();
try {
final updatedModel = await _modelUseCase.updateModel(
id: id,
vendorsId: vendorsId,
name: name,
);
// 목록에서 업데이트
final index = _models.indexWhere((m) => m.id == id);
if (index != -1) {
_models = _models.map((model) =>
model.id == id ? updatedModel : model
).toList();
_applyFilters();
await _groupModelsByVendor();
}
return true;
} catch (e) {
_errorMessage = e.toString();
return false;
} finally {
_isSubmitting = false;
notifyListeners();
}
}
/// 모델 삭제 (Soft Delete)
Future<bool> deleteModel(int id) async {
_isSubmitting = true;
_errorMessage = null;
notifyListeners();
try {
await _modelUseCase.deleteModel(id);
// 목록에서 제거 또는 비활성화 표시
_models = _models.where((m) => m.id != id).toList();
_applyFilters();
await _groupModelsByVendor();
return true;
} catch (e) {
_errorMessage = e.toString();
return false;
} finally {
_isSubmitting = false;
notifyListeners();
}
}
/// 검색어 설정
void setSearchQuery(String query) {
_searchQuery = query;
_applyFilters();
notifyListeners();
}
/// Vendor 필터 설정
void setVendorFilter(int? vendorId) {
_selectedVendorId = vendorId;
_applyFilters();
notifyListeners();
}
/// 필터 적용
void _applyFilters() {
_filteredModels = _models.where((model) {
// Vendor 필터
if (_selectedVendorId != null && model.vendorsId != _selectedVendorId) {
return false;
}
// 검색어 필터
if (_searchQuery.isNotEmpty) {
final query = _searchQuery.toLowerCase();
return model.name.toLowerCase().contains(query);
}
return true;
}).toList();
}
/// Vendor별 모델 그룹핑
Future<void> _groupModelsByVendor() async {
_modelsByVendor.clear();
for (final model in _models) {
_modelsByVendor.putIfAbsent(model.vendorsId, () => []).add(model);
}
}
/// Vendor ID로 Vendor 정보 가져오기
VendorDto? getVendorById(int vendorId) {
try {
return _vendors.firstWhere((v) => v.id == vendorId);
} catch (_) {
return null;
}
}
/// 특정 Vendor의 모델 목록 가져오기
List<ModelDto> getModelsByVendor(int vendorId) {
return _modelsByVendor[vendorId] ?? [];
}
/// 에러 메시지 클리어
void clearError() {
_errorMessage = null;
notifyListeners();
}
/// 컨트롤러 리셋
void reset() {
_models.clear();
_filteredModels.clear();
_vendors.clear();
_modelsByVendor.clear();
_isLoading = false;
_isSubmitting = false;
_errorMessage = null;
_searchQuery = '';
_selectedVendorId = null;
notifyListeners();
}
@override
void dispose() {
reset();
super.dispose();
}
}

View File

@@ -0,0 +1,189 @@
import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:superport/data/models/model_dto.dart';
import 'package:superport/screens/model/controllers/model_controller.dart';
class ModelFormDialog extends StatefulWidget {
final ModelController controller;
final ModelDto? model;
const ModelFormDialog({
super.key,
required this.controller,
this.model,
});
@override
State<ModelFormDialog> createState() => _ModelFormDialogState();
}
class _ModelFormDialogState extends State<ModelFormDialog> {
final _formKey = GlobalKey<FormState>();
late final TextEditingController _nameController;
int? _selectedVendorId;
bool _isSubmitting = false;
@override
void initState() {
super.initState();
_nameController = TextEditingController(text: widget.model?.name);
_selectedVendorId = widget.model?.vendorsId;
}
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isEditMode = widget.model != null;
return ShadDialog(
title: Text(isEditMode ? '모델 수정' : '새 모델 등록'),
child: Container(
width: 400,
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Vendor 선택
ShadSelect<int>(
placeholder: const Text('제조사 선택'),
options: widget.controller.vendors.map(
(vendor) => ShadOption(
value: vendor.id,
child: Text(vendor.name),
),
).toList(),
selectedOptionBuilder: (context, value) {
final vendor = widget.controller.vendors.firstWhere(
(v) => v.id == value,
);
return Text(vendor.name);
},
onChanged: (value) {
setState(() {
_selectedVendorId = value;
});
},
initialValue: _selectedVendorId,
),
const SizedBox(height: 16),
// 모델명 입력
ShadInputFormField(
controller: _nameController,
label: const Text('모델명'),
placeholder: const Text('모델명을 입력하세요'),
validator: (value) {
if (value.isEmpty) {
return '모델명은 필수입니다';
}
return null;
},
),
const SizedBox(height: 16),
// 활성 상태는 백엔드에서 관리하므로 UI에서 제거
// 버튼들
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ShadButton.outline(
onPressed: _isSubmitting ? null : () {
Navigator.of(context).pop();
},
child: const Text('취소'),
),
const SizedBox(width: 8),
ShadButton(
onPressed: _isSubmitting ? null : _handleSubmit,
child: _isSubmitting
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: Text(isEditMode ? '수정' : '등록'),
),
],
),
],
),
),
),
);
}
Future<void> _handleSubmit() async {
if (!_formKey.currentState!.validate()) {
return;
}
if (_selectedVendorId == null) {
ShadToaster.of(context).show(
const ShadToast(
title: Text('오류'),
description: Text('제조사를 선택해주세요'),
backgroundColor: Colors.red,
),
);
return;
}
setState(() {
_isSubmitting = true;
});
bool success;
if (widget.model != null) {
// 수정
success = await widget.controller.updateModel(
id: widget.model!.id!,
vendorsId: _selectedVendorId!,
name: _nameController.text,
);
} else {
// 생성
success = await widget.controller.createModel(
vendorsId: _selectedVendorId!,
name: _nameController.text,
);
}
setState(() {
_isSubmitting = false;
});
if (mounted) {
if (success) {
Navigator.of(context).pop();
ShadToaster.of(context).show(
ShadToast(
title: const Text('성공'),
description: Text(
widget.model != null
? '모델이 수정되었습니다.'
: '모델이 등록되었습니다.',
),
),
);
} else {
ShadToaster.of(context).show(
ShadToast(
title: const Text('오류'),
description: Text(widget.controller.errorMessage ?? '처리 실패'),
backgroundColor: Colors.red,
),
);
}
}
}
}

View File

@@ -0,0 +1,311 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:superport/data/models/model_dto.dart';
import 'package:superport/screens/model/controllers/model_controller.dart';
import 'package:superport/screens/model/model_form_dialog.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: Consumer<ModelController>(
builder: (context, controller, _) {
return ShadCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
const SizedBox(height: 16),
_buildFilters(),
const SizedBox(height: 16),
if (controller.errorMessage != null) ...[
ShadAlert(
icon: const Icon(Icons.error),
title: const Text('오류'),
description: Text(controller.errorMessage!),
),
const SizedBox(height: 16),
],
Expanded(
child: controller.isLoading
? const Center(child: CircularProgressIndicator())
: _buildModelTable(),
),
],
),
);
},
),
);
}
Widget _buildHeader() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'모델 관리',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
ShadButton(
onPressed: () => _showCreateDialog(),
child: const Row(
children: [
Icon(Icons.add, size: 16),
SizedBox(width: 8),
Text('새 모델 등록'),
],
),
),
],
);
}
Widget _buildFilters() {
return Consumer<ModelController>(
builder: (context, controller, _) {
return Row(
children: [
Expanded(
flex: 2,
child: ShadInput(
placeholder: const Text('모델명 검색...'),
onChanged: controller.setSearchQuery,
),
),
const SizedBox(width: 16),
Expanded(
child: ShadSelect<int?>(
placeholder: const Text('제조사 선택'),
options: [
const ShadOption(
value: null,
child: Text('전체'),
),
...controller.vendors.map(
(vendor) => ShadOption(
value: vendor.id,
child: Text(vendor.name),
),
),
],
selectedOptionBuilder: (context, value) {
if (value == null) {
return const Text('전체');
}
final vendor = controller.vendors.firstWhere((v) => v.id == value);
return Text(vendor.name);
},
onChanged: controller.setVendorFilter,
),
),
const SizedBox(width: 16),
ShadButton.outline(
onPressed: controller.refreshModels,
child: const Icon(Icons.refresh),
),
],
);
},
);
}
Widget _buildModelTable() {
return Consumer<ModelController>(
builder: (context, controller, _) {
if (controller.models.isEmpty) {
return const Center(
child: Text('등록된 모델이 없습니다.'),
);
}
return ShadTable(
builder: (context, tableVicinity) {
final row = tableVicinity.row;
final column = tableVicinity.column;
// Header
if (row == 0) {
const headers = ['ID', '제조사', '모델명', '설명', '상태', '작업'];
return ShadTableCell(
child: Container(
padding: const EdgeInsets.all(12),
color: Colors.grey.shade100,
child: Text(
headers[column],
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
);
}
// Data rows
final modelIndex = row - 1;
if (modelIndex < controller.models.length) {
final model = controller.models[modelIndex];
final vendor = controller.getVendorById(model.vendorsId);
switch (column) {
case 0:
return ShadTableCell(
child: Container(
padding: const EdgeInsets.all(12),
child: Text(model.id.toString()),
),
);
case 1:
return ShadTableCell(
child: Container(
padding: const EdgeInsets.all(12),
child: Text(vendor?.name ?? 'Unknown'),
),
);
case 2:
return ShadTableCell(
child: Container(
padding: const EdgeInsets.all(12),
child: Text(model.name),
),
);
case 3:
return ShadTableCell(
child: Container(
padding: const EdgeInsets.all(12),
child: Text('-'),
),
);
case 4:
return ShadTableCell(
child: Container(
padding: const EdgeInsets.all(12),
child: ShadBadge(
backgroundColor: model.isActive
? Colors.green.shade100
: Colors.grey.shade200,
child: Text(
model.isActive ? '활성' : '비활성',
style: TextStyle(
color: model.isActive ? Colors.green.shade700 : Colors.grey.shade700,
),
),
),
),
);
case 5:
return ShadTableCell(
child: Container(
padding: const EdgeInsets.all(8),
child: Row(
children: [
ShadButton.ghost(
size: ShadButtonSize.sm,
onPressed: () => _showEditDialog(model),
child: const Icon(Icons.edit, size: 16),
),
const SizedBox(width: 8),
ShadButton.ghost(
size: ShadButtonSize.sm,
onPressed: () => _showDeleteConfirmation(model),
child: const Icon(Icons.delete, size: 16),
),
],
),
),
);
default:
return const ShadTableCell(child: SizedBox());
}
}
return const ShadTableCell(child: SizedBox());
},
rowCount: controller.models.length + 1, // +1 for header
columnCount: 6,
);
},
);
}
void _showCreateDialog() {
showShadDialog(
context: context,
builder: (context) => ModelFormDialog(
controller: _controller,
),
);
}
void _showEditDialog(ModelDto model) {
showShadDialog(
context: context,
builder: (context) => ModelFormDialog(
controller: _controller,
model: model,
),
);
}
void _showDeleteConfirmation(ModelDto model) {
showShadDialog(
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 {
final success = await _controller.deleteModel(model.id!);
if (context.mounted) {
Navigator.of(context).pop();
if (success) {
ShadToaster.of(context).show(
const ShadToast(
title: Text('성공'),
description: Text('모델이 삭제되었습니다.'),
),
);
} else {
ShadToaster.of(context).show(
ShadToast(
title: const Text('오류'),
description: Text(_controller.errorMessage ?? '삭제 실패'),
backgroundColor: Colors.red,
),
);
}
}
},
child: const Text('삭제'),
),
],
),
);
}
}