293 lines
12 KiB
Dart
293 lines
12 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';
|
|
|
|
class StockInForm extends StatefulWidget {
|
|
const StockInForm({super.key});
|
|
|
|
@override
|
|
State<StockInForm> createState() => _StockInFormState();
|
|
}
|
|
|
|
class _StockInFormState extends State<StockInForm> {
|
|
final _formKey = GlobalKey<ShadFormState>();
|
|
|
|
int? _selectedEquipmentId;
|
|
int? _selectedWarehouseId;
|
|
int _quantity = 1;
|
|
DateTime _transactionDate = DateTime.now();
|
|
String? _notes;
|
|
String _status = 'available'; // 장비 상태
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
// 장비 목록 로드
|
|
context.read<EquipmentListController>().refresh();
|
|
});
|
|
}
|
|
|
|
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),
|
|
|
|
// 창고 선택
|
|
ShadSelect<int>(
|
|
placeholder: const Text('창고 선택'),
|
|
options: [
|
|
const ShadOption(value: 1, child: Text('본사 창고')),
|
|
const ShadOption(value: 2, child: Text('지사 창고')),
|
|
const ShadOption(value: 3, child: Text('외부 창고')),
|
|
],
|
|
selectedOptionBuilder: (context, value) {
|
|
switch (value) {
|
|
case 1:
|
|
return const Text('본사 창고');
|
|
case 2:
|
|
return const Text('지사 창고');
|
|
case 3:
|
|
return const Text('외부 창고');
|
|
default:
|
|
return const Text('');
|
|
}
|
|
},
|
|
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),
|
|
|
|
// 상태
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text('상태', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
|
|
const SizedBox(height: 8),
|
|
ShadSelect<String>(
|
|
placeholder: const Text('상태 선택'),
|
|
initialValue: 'available',
|
|
options: const [
|
|
ShadOption(value: 'available', child: Text('사용 가능')),
|
|
ShadOption(value: 'in_use', child: Text('사용 중')),
|
|
ShadOption(value: 'maintenance', child: Text('정비 중')),
|
|
ShadOption(value: 'reserved', child: Text('예약됨')),
|
|
],
|
|
selectedOptionBuilder: (context, value) {
|
|
switch (value) {
|
|
case 'available':
|
|
return const Text('사용 가능');
|
|
case 'in_use':
|
|
return const Text('사용 중');
|
|
case 'maintenance':
|
|
return const Text('정비 중');
|
|
case 'reserved':
|
|
return const Text('예약됨');
|
|
default:
|
|
return const Text('');
|
|
}
|
|
},
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_status = value ?? 'available';
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
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('입고 등록'),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
} |