Files
superport/lib/screens/inventory/stock_in_form.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

344 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import '../../screens/equipment/controllers/equipment_history_controller.dart';
import '../../screens/equipment/controllers/equipment_list_controller.dart';
import '../../data/models/equipment_history_dto.dart';
import '../../data/models/warehouse/warehouse_dto.dart';
import '../../services/warehouse_service.dart';
import 'package:get_it/get_it.dart';
class StockInForm extends StatefulWidget {
const StockInForm({super.key});
@override
State<StockInForm> createState() => _StockInFormState();
}
class _StockInFormState extends State<StockInForm> {
final _formKey = GlobalKey<ShadFormState>();
final _warehouseService = GetIt.instance<WarehouseService>();
int? _selectedEquipmentId;
int? _selectedWarehouseId;
int _quantity = 1;
DateTime _transactionDate = DateTime.now();
String? _notes;
// 창고 관련 상태
List<WarehouseDto> _warehouses = [];
bool _isLoadingWarehouses = false;
String? _warehouseError;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// 장비 목록 로드
context.read<EquipmentListController>().refresh();
// 창고 목록 로드
_loadWarehouses();
});
}
/// 사용 중인 창고 목록 로드
Future<void> _loadWarehouses() async {
setState(() {
_isLoadingWarehouses = true;
_warehouseError = null;
});
try {
final warehouses = await _warehouseService.getInUseWarehouseLocations();
if (mounted) {
setState(() {
// WarehouseLocation을 WarehouseDto로 변환 (임시 - 추후 DTO 직접 사용 메서드 추가 예정)
_warehouses = warehouses.map((location) => WarehouseDto(
id: location.id,
name: location.name,
remark: location.remark,
isDeleted: !location.isActive,
)).toList();
_isLoadingWarehouses = false;
});
}
} catch (e) {
if (mounted) {
setState(() {
_warehouseError = '창고 목록을 불러오는데 실패했습니다: ${e.toString()}';
_isLoadingWarehouses = false;
});
}
}
}
Future<void> _handleSubmit() async {
if (_formKey.currentState?.saveAndValidate() ?? false) {
final controller = context.read<EquipmentHistoryController>();
// 백엔드 스키마에 맞는 요청 데이터 생성
final request = EquipmentHistoryRequestDto(
equipmentsId: _selectedEquipmentId!,
warehousesId: _selectedWarehouseId ?? 1, // 기본 창고 ID
transactionType: 'I', // 입고
quantity: _quantity,
transactedAt: _transactionDate,
remark: _notes,
);
await controller.createHistory(request);
if (controller.error == null && mounted) {
ShadToaster.of(context).show(
const ShadToast(
title: Text('입고 등록 완료'),
description: Text('장비가 성공적으로 입고되었습니다'),
),
);
Navigator.pop(context, true);
} else if (controller.error != null && mounted) {
ShadToaster.of(context).show(
ShadToast.destructive(
title: const Text('입고 등록 실패'),
description: Text(controller.error!),
),
);
}
}
}
@override
Widget build(BuildContext context) {
final theme = ShadTheme.of(context);
return Scaffold(
backgroundColor: theme.colorScheme.background,
appBar: AppBar(
backgroundColor: theme.colorScheme.card,
title: const Text('장비 입고 등록'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 600),
child: ShadCard(
child: Padding(
padding: const EdgeInsets.all(24),
child: ShadForm(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'입고 정보 입력',
style: theme.textTheme.h3,
),
const SizedBox(height: 8),
Text(
'입고할 장비와 창고 정보를 입력하세요',
style: theme.textTheme.muted,
),
const SizedBox(height: 24),
// 장비 선택
Consumer<EquipmentListController>(
builder: (context, controller, _) {
return ShadSelect<int>(
placeholder: const Text('장비 선택'),
options: controller.equipments.map((equipment) {
return ShadOption(
value: equipment.id!,
child: Text('${equipment.equipment.modelDto?.name ?? 'Unknown'} (${equipment.equipment.serialNumber})'),
);
}).toList(),
selectedOptionBuilder: (context, value) {
final equipment = controller.equipments.firstWhere(
(e) => e.id == value,
);
return Text('${equipment.equipment.modelDto?.name ?? 'Unknown'} (${equipment.equipment.serialNumber})');
},
onChanged: (value) {
setState(() {
_selectedEquipmentId = value;
});
},
);
},
),
const SizedBox(height: 16),
// 창고 선택 (백엔드 API 연동)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('창고', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
const SizedBox(height: 8),
if (_isLoadingWarehouses)
Container(
height: 40,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
child: const Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
),
SizedBox(width: 8),
Text('창고 목록 로딩 중...', style: TextStyle(fontSize: 14)),
],
),
),
)
else if (_warehouseError != null)
Column(
children: [
Container(
height: 40,
decoration: BoxDecoration(
border: Border.all(color: Colors.red.shade300),
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
_warehouseError!,
style: TextStyle(color: Colors.red.shade600, fontSize: 14),
),
),
),
const SizedBox(height: 8),
ShadButton.outline(
onPressed: _loadWarehouses,
child: const Text('재시도'),
),
],
)
else
ShadSelect<int>(
placeholder: const Text('창고 선택'),
options: _warehouses.map((warehouse) {
return ShadOption(
value: warehouse.id!,
child: Text(warehouse.name),
);
}).toList(),
selectedOptionBuilder: (context, value) {
final warehouse = _warehouses.firstWhere(
(w) => w.id == value,
orElse: () => WarehouseDto(name: 'Unknown'),
);
return Text(warehouse.name);
},
onChanged: (value) {
setState(() {
_selectedWarehouseId = value;
});
},
),
],
),
const SizedBox(height: 16),
// 수량
ShadInputFormField(
label: const Text('수량'),
initialValue: '1',
keyboardType: TextInputType.number,
validator: (v) {
if (_quantity <= 0) {
return '수량은 1 이상이어야 합니다';
}
return null;
},
onChanged: (value) {
setState(() {
_quantity = int.tryParse(value) ?? 1;
});
},
),
const SizedBox(height: 16),
// 입고일
ShadInputFormField(
label: const Text('입고일'),
initialValue: '${_transactionDate.year}-${_transactionDate.month.toString().padLeft(2, '0')}-${_transactionDate.day.toString().padLeft(2, '0')}',
enabled: false,
trailing: IconButton(
icon: const Icon(Icons.calendar_today, size: 16),
onPressed: () async {
final picked = await showDatePicker(
context: context,
initialDate: _transactionDate,
firstDate: DateTime(2020),
lastDate: DateTime.now(),
);
if (picked != null) {
setState(() {
_transactionDate = picked;
});
}
},
),
),
const SizedBox(height: 16),
// 비고
ShadInputFormField(
label: const Text('비고'),
maxLines: 3,
placeholder: const Text('추가 정보를 입력하세요'),
onChanged: (value) {
_notes = value.isEmpty ? null : value;
},
),
const SizedBox(height: 32),
// 버튼
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ShadButton.outline(
child: const Text('취소'),
onPressed: () => Navigator.pop(context),
),
const SizedBox(width: 8),
Consumer<EquipmentHistoryController>(
builder: (context, controller, _) {
return ShadButton(
enabled: !controller.isLoading,
onPressed: _handleSubmit,
child: controller.isLoading
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Text('입고 등록'),
);
},
),
],
),
],
),
),
),
),
),
),
),
);
}
}