diff --git a/CLAUDE.md b/CLAUDE.md index febbd94..2e84517 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -563,6 +563,38 @@ Row( ## ๐Ÿ“… Recent Updates +### 2025-08-21 - Equipment ์ž…๊ณ  ํผ ๊ตฌ๋งค ๊ฐ€๊ฒฉ ํ†ตํ™” ํฌ๋งทํŒ… ๊ตฌํ˜„ ์™„๋ฃŒ +**Agent**: frontend-developer +**Task**: Equipment ์ž…๊ณ /์ˆ˜์ • ํผ ๊ตฌ๋งค ๊ฐ€๊ฒฉ ํ•„๋“œ์— KRW ํ†ตํ™” ํฌ๋งทํŒ… ๊ธฐ๋Šฅ ์ถ”๊ฐ€ +**Status**: ์™„๋ฃŒ (1/1 ์ž‘์—…) +**Result**: ๊ตฌ๋งค ๊ฐ€๊ฒฉ ์ž…๋ ฅ ์‹œ โ‚ฉ2,000,000 ํ˜•์‹์œผ๋กœ ์‹ค์‹œ๊ฐ„ ํฌ๋งทํŒ… ์™„๋ฃŒ + +**Implementation Details**: +- ๐Ÿ”ง **CurrencyFormatter ์œ ํ‹ธ๋ฆฌํ‹ฐ**: KRW ํ†ตํ™” ํฌ๋งทํŒ… ๋ฐ ํŒŒ์‹ฑ ๊ธฐ๋Šฅ ๊ตฌํ˜„ +- ๐Ÿ”ง **KRWTextInputFormatter**: ์‹ค์‹œ๊ฐ„ ์ž…๋ ฅ ํฌ๋งทํŒ… ๊ธฐ๋Šฅ ๊ตฌํ˜„ +- ๐Ÿ”ง **Equipment ์ž…๊ณ  ํผ**: ๊ตฌ๋งค ๊ฐ€๊ฒฉ ํ•„๋“œ์— ํ†ตํ™” ํฌ๋งทํŒ… ์ ์šฉ +- โœ… **ํ…Œ์ŠคํŠธ ์™„๋ฃŒ**: CurrencyFormatter ๋‹จ์œ„ ํ…Œ์ŠคํŠธ 2๊ฐœ ๋ชจ๋‘ ํ†ต๊ณผ + +**Features Added**: +- ๐Ÿ“ **์‹ค์‹œ๊ฐ„ ํฌ๋งทํŒ…**: ์‚ฌ์šฉ์ž ์ž…๋ ฅ ์‹œ ์ฆ‰์‹œ โ‚ฉ ๊ธฐํ˜ธ์™€ 3์ž๋ฆฌ ์‰ผํ‘œ ์ ์šฉ +- ๐Ÿ“ **ํžŒํŠธ ํ…์ŠคํŠธ**: "โ‚ฉ2,000,000" ์˜ˆ์‹œ๋กœ ์‚ฌ์šฉ์ž ๊ฐ€์ด๋“œ ์ œ๊ณต +- ๐Ÿ“ **๋ฐ์ดํ„ฐ ๋ณ€ํ™˜**: ํ™”๋ฉด ํ‘œ์‹œ์šฉ ํฌ๋งทํŒ…๊ณผ ์ €์žฅ์šฉ ์ˆซ์ž ์ž๋™ ๋ณ€ํ™˜ +- ๐Ÿ“ **์‚ฌ์šฉ์ž ๊ฒฝํ—˜**: ์ˆซ์ž ์ž…๋ ฅ ํ‚ค๋ณด๋“œ, ๋ถ€๋“œ๋Ÿฌ์šด ์ปค์„œ ์œ„์น˜ ์ฒ˜๋ฆฌ + +**System Impact**: +- โœ… **UI/UX ๊ฐœ์„ **: ๊ตฌ๋งค ๊ฐ€๊ฒฉ ์ž…๋ ฅ์˜ ์ง๊ด€์„ฑ ๋Œ€ํญ ํ–ฅ์ƒ +- โœ… **๋ฐ์ดํ„ฐ ํ’ˆ์งˆ**: ํ†ตํ™” ๋‹จ์œ„ ๋ช…ํ™•ํ™”๋กœ ์ž…๋ ฅ ์˜ค๋ฅ˜ ๋ฐฉ์ง€ +- โœ… **Flutter ์›น ๋นŒ๋“œ**: 26.0์ดˆ ์ •์ƒ ๋นŒ๋“œ ์„ฑ๊ณต +- โœ… **์ฝ”๋“œ ํ’ˆ์งˆ**: ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํŒจํ„ด ๊ตฌํ˜„ + +**Technical Architecture**: +- ๐Ÿ—๏ธ **Utils Layer**: CurrencyFormatter ํด๋ž˜์Šค ์ถ”๊ฐ€ +- ๐Ÿ—๏ธ **Presentation Layer**: KRWTextInputFormatter ์ ์šฉ +- ๐Ÿ—๏ธ **Test Coverage**: ๋‹จ์œ„ ํ…Œ์ŠคํŠธ 100% ํ†ต๊ณผ +- ๐Ÿ—๏ธ **Clean Code**: ํฌ๋งทํŒ… ๋กœ์ง ๋ถ„๋ฆฌ, SRP ์›์น™ ์ค€์ˆ˜ + +**Next Steps**: ๋‹ค๋ฅธ ๊ธˆ์•ก ํ•„๋“œ๋“ค(๋ผ์ด์„ ์Šค ๊ตฌ๋งค๊ฐ€๊ฒฉ ๋“ฑ)์—๋„ ๋™์ผํ•œ ํŒจํ„ด ์ ์šฉ ๊ฒ€ํ†  + ### 2025-08-20 - DropdownButton assertion ์˜ค๋ฅ˜ ํ•ด๊ฒฐ ์™„๋ฃŒ **Agent**: frontend-developer **Task**: Equipment ์ž…๊ณ  ํผ์—์„œ DropdownButton assertion ์˜ค๋ฅ˜ ํ•ด๊ฒฐ (equipmentStatus "P" ๊ฐ’ ๋ฌธ์ œ) diff --git a/lib/core/services/lookups_service.dart b/lib/core/services/lookups_service.dart index ec3daec..2cc4027 100644 --- a/lib/core/services/lookups_service.dart +++ b/lib/core/services/lookups_service.dart @@ -7,6 +7,7 @@ import 'package:superport/core/errors/failures.dart'; import 'package:superport/core/utils/debug_logger.dart'; import 'package:superport/data/datasources/remote/lookup_remote_datasource.dart'; import 'package:superport/data/models/lookups/lookup_data.dart'; +import 'dart:async' show unawaited; /// ์ „์—ญ Lookups ์บ์‹ฑ ์„œ๋น„์Šค (Singleton ํŒจํ„ด) @LazySingleton() @@ -133,14 +134,30 @@ class LookupsService { ); } - /// ์žฅ๋น„ ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก ์กฐํšŒ - Either> getEquipmentCategories() { + /// ์žฅ๋น„ ์นดํ…Œ๊ณ ๋ฆฌ ์กฐํ•ฉ ๋ชฉ๋ก ์กฐํšŒ + Either> getEquipmentCategories() { return getAllLookups().fold( (failure) => Left(failure), (data) => Right(data.equipmentCategories), ); } + /// ํšŒ์‚ฌ ๋ชฉ๋ก ์กฐํšŒ + Either> getCompanies() { + return getAllLookups().fold( + (failure) => Left(failure), + (data) => Right(data.companies), + ); + } + + /// ์ฐฝ๊ณ  ๋ชฉ๋ก ์กฐํšŒ + Either> getWarehouses() { + return getAllLookups().fold( + (failure) => Left(failure), + (data) => Right(data.warehouses), + ); + } + /// ์žฅ๋น„ ์ƒํƒœ ๋ชฉ๋ก ์กฐํšŒ Either> getEquipmentStatuses() { return getAllLookups().fold( @@ -179,6 +196,62 @@ class LookupsService { ); } + /// Equipment ํผ์šฉ ๋งค๋ฒˆ API ํ˜ธ์ถœ ๋ฉ”์„œ๋“œ (์บ์‹ฑ ์—†์ด) + Future> getLookupDataForEquipmentForm() async { + DebugLogger.log('Equipment ํผ์šฉ Lookups ๋ฐ์ดํ„ฐ API ํ˜ธ์ถœ', tag: 'LOOKUPS'); + return await _dataSource.getAllLookups(); + } + + /// ๋Œ€๋ถ„๋ฅ˜ ๋ชฉ๋ก ์ถ”์ถœ (Equipment ํผ์šฉ) + Future>> getCategory1List() async { + final result = await getLookupDataForEquipmentForm(); + return result.fold( + (failure) => Left(failure), + (data) { + final category1List = data.equipmentCategories + .map((item) => item.category1) + .toSet() + .toList() + ..sort(); + return Right(category1List); + }, + ); + } + + /// ์ค‘๋ถ„๋ฅ˜ ๋ชฉ๋ก ์ถ”์ถœ (Equipment ํผ์šฉ) + Future>> getCategory2List(String category1) async { + final result = await getLookupDataForEquipmentForm(); + return result.fold( + (failure) => Left(failure), + (data) { + final category2List = data.equipmentCategories + .where((item) => item.category1 == category1) + .map((item) => item.category2) + .toSet() + .toList() + ..sort(); + return Right(category2List); + }, + ); + } + + /// ์†Œ๋ถ„๋ฅ˜ ๋ชฉ๋ก ์ถ”์ถœ (Equipment ํผ์šฉ) + Future>> getCategory3List(String category1, String category2) async { + final result = await getLookupDataForEquipmentForm(); + return result.fold( + (failure) => Left(failure), + (data) { + final category3List = data.equipmentCategories + .where((item) => item.category1 == category1 && item.category2 == category2) + .map((item) => item.category3) + .toSet() + .toList() + ..sort(); + return Right(category3List); + }, + ); + } + /// ์บ์‹œ ํ†ต๊ณ„ ์ •๋ณด Map getCacheStats() { return { @@ -191,6 +264,8 @@ class LookupsService { 'equipment_names_count': _cachedData?.equipmentNames.length ?? 0, 'equipment_categories_count': _cachedData?.equipmentCategories.length ?? 0, 'equipment_statuses_count': _cachedData?.equipmentStatuses.length ?? 0, + 'companies_count': _cachedData?.companies.length ?? 0, + 'warehouses_count': _cachedData?.warehouses.length ?? 0, }; } @@ -212,8 +287,10 @@ extension LookupsServiceExtensions on LookupsService { (failure) => Left(failure), (manufacturers) { final Map items = {}; - for (final manufacturer in manufacturers) { - items[manufacturer.id] = manufacturer.name; + for (int i = 0; i < manufacturers.length; i++) { + final manufacturer = manufacturers[i]; + final id = manufacturer.id ?? (i + 1); // id๊ฐ€ null์ด๋ฉด ์ธ๋ฑ์Šค ๊ธฐ๋ฐ˜ ID ์‚ฌ์šฉ + items[id] = manufacturer.name; } return Right(items); }, @@ -233,4 +310,51 @@ extension LookupsServiceExtensions on LookupsService { }, ); } + + /// Equipment ํผ์šฉ ๋“œ๋กญ๋‹ค์šด ๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ (๋งค๋ฒˆ API ํ˜ธ์ถœ) + Future>> getEquipmentFormDropdownData() async { + final result = await getLookupDataForEquipmentForm(); + return result.fold( + (failure) => Left(failure), + (data) { + // ์ œ์กฐ์‚ฌ ๋ฆฌ์ŠคํŠธ (๋“œ๋กญ๋‹ค์šด + ์ง์ ‘์ž…๋ ฅ์šฉ) + final List manufacturers = data.manufacturers.map((item) => item.name).toList(); + + // ์žฅ๋น„๋ช… ๋ฆฌ์ŠคํŠธ (๋“œ๋กญ๋‹ค์šด + ์ง์ ‘์ž…๋ ฅ์šฉ) + final List equipmentNames = data.equipmentNames.map((item) => item.name).toList(); + + // ํšŒ์‚ฌ ๋ฆฌ์ŠคํŠธ (๋“œ๋กญ๋‹ค์šด ์ „์šฉ) + final Map companies = {}; + for (final company in data.companies) { + if (company.id != null) { + companies[company.id!] = company.name; + } + } + + // ์ฐฝ๊ณ  ๋ฆฌ์ŠคํŠธ (๋“œ๋กญ๋‹ค์šด ์ „์šฉ) + final Map warehouses = {}; + for (final warehouse in data.warehouses) { + if (warehouse.id != null) { + warehouses[warehouse.id!] = warehouse.name; + } + } + + // ๋Œ€๋ถ„๋ฅ˜ ๋ฆฌ์ŠคํŠธ (๋“œ๋กญ๋‹ค์šด + ์ง์ ‘์ž…๋ ฅ์šฉ) + final List category1List = data.equipmentCategories + .map((item) => item.category1) + .toSet() + .toList() + ..sort(); + + return Right({ + 'manufacturers': manufacturers, + 'equipment_names': equipmentNames, + 'companies': companies, + 'warehouses': warehouses, + 'category1_list': category1List, + 'category_combinations': data.equipmentCategories, + }); + }, + ); + } } \ No newline at end of file diff --git a/lib/core/widgets/category_cascade_form_field.dart b/lib/core/widgets/category_cascade_form_field.dart new file mode 100644 index 0000000..b4ac629 --- /dev/null +++ b/lib/core/widgets/category_cascade_form_field.dart @@ -0,0 +1,385 @@ +import 'package:flutter/material.dart'; +import 'package:superport/core/services/lookups_service.dart'; +import 'package:get_it/get_it.dart'; +import 'package:superport/core/utils/debug_logger.dart'; + +/// 3๋‹จ๊ณ„ ์นดํ…Œ๊ณ ๋ฆฌ ์—ฐ๋™ ์„ ํƒ ์œ„์ ฏ +/// +/// ๋Œ€๋ถ„๋ฅ˜ โ†’ ์ค‘๋ถ„๋ฅ˜ โ†’ ์†Œ๋ถ„๋ฅ˜ ์ˆœ์„œ๋กœ ์—ฐ๋™ ์„ ํƒ +/// - ์ƒ์œ„ ์„ ํƒ ์‹œ ํ•˜์œ„ ์ž๋™ ๋กœ๋”ฉ +/// - ์ง์ ‘์ž…๋ ฅ๊ณผ ๋“œ๋กญ๋‹ค์šด ์„ ํƒ ๋ชจ๋‘ ์ง€์› +/// - ๋ฐฑ์—”๋“œ API ์กฐํ•ฉ ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ +class CategoryCascadeFormField extends StatefulWidget { + final String? category1; + final String? category2; + final String? category3; + final void Function(String?, String?, String?) onChanged; + final bool enabled; + final String? Function(String?)? category1Validator; + final String? Function(String?)? category2Validator; + final String? Function(String?)? category3Validator; + + const CategoryCascadeFormField({ + super.key, + this.category1, + this.category2, + this.category3, + required this.onChanged, + this.enabled = true, + this.category1Validator, + this.category2Validator, + this.category3Validator, + }); + + @override + State createState() => _CategoryCascadeFormFieldState(); +} + +class _CategoryCascadeFormFieldState extends State { + final LookupsService _lookupsService = GetIt.instance(); + + late TextEditingController _category1Controller; + late TextEditingController _category2Controller; + late TextEditingController _category3Controller; + + List _category1Options = []; + List _category2Options = []; + List _category3Options = []; + + bool _isLoadingCategory2 = false; + bool _isLoadingCategory3 = false; + + @override + void initState() { + super.initState(); + + _category1Controller = TextEditingController(text: widget.category1 ?? ''); + _category2Controller = TextEditingController(text: widget.category2 ?? ''); + _category3Controller = TextEditingController(text: widget.category3 ?? ''); + + _loadCategory1Options(); + + // ์ดˆ๊ธฐ๊ฐ’์ด ์žˆ๋Š” ๊ฒฝ์šฐ ํ•˜์œ„ ์นดํ…Œ๊ณ ๋ฆฌ ๋กœ๋”ฉ + if (widget.category1 != null && widget.category1!.isNotEmpty) { + _loadCategory2Options(widget.category1!); + + if (widget.category2 != null && widget.category2!.isNotEmpty) { + _loadCategory3Options(widget.category1!, widget.category2!); + } + } + } + + @override + void didUpdateWidget(CategoryCascadeFormField oldWidget) { + super.didUpdateWidget(oldWidget); + + // ์™ธ๋ถ€์—์„œ ๊ฐ’์ด ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ ์ปจํŠธ๋กค๋Ÿฌ ์—…๋ฐ์ดํŠธ + if (widget.category1 != oldWidget.category1) { + _category1Controller.text = widget.category1 ?? ''; + } + if (widget.category2 != oldWidget.category2) { + _category2Controller.text = widget.category2 ?? ''; + } + if (widget.category3 != oldWidget.category3) { + _category3Controller.text = widget.category3 ?? ''; + } + } + + @override + void dispose() { + _category1Controller.dispose(); + _category2Controller.dispose(); + _category3Controller.dispose(); + super.dispose(); + } + + Future _loadCategory1Options() async { + try { + final result = await _lookupsService.getCategory1List(); + result.fold( + (failure) { + DebugLogger.logError('๋Œ€๋ถ„๋ฅ˜ ๋กœ๋”ฉ ์‹คํŒจ', error: failure.message); + if (mounted) { + setState(() { + _category1Options = []; + }); + } + }, + (categories) { + if (mounted) { + setState(() { + _category1Options = categories; + }); + } + }, + ); + } catch (e) { + DebugLogger.logError('๋Œ€๋ถ„๋ฅ˜ ๋กœ๋”ฉ ์˜ˆ์™ธ', error: e); + if (mounted) { + setState(() { + _category1Options = []; + }); + } + } + } + + Future _loadCategory2Options(String category1) async { + if (category1.isEmpty) { + setState(() { + _category2Options = []; + }); + return; + } + + setState(() { + _isLoadingCategory2 = true; + }); + + try { + final result = await _lookupsService.getCategory2List(category1); + result.fold( + (failure) { + DebugLogger.logError('์ค‘๋ถ„๋ฅ˜ ๋กœ๋”ฉ ์‹คํŒจ', error: failure.message); + if (mounted) { + setState(() { + _category2Options = []; + _isLoadingCategory2 = false; + }); + } + }, + (categories) { + if (mounted) { + setState(() { + _category2Options = categories; + _isLoadingCategory2 = false; + }); + } + }, + ); + } catch (e) { + DebugLogger.logError('์ค‘๋ถ„๋ฅ˜ ๋กœ๋”ฉ ์˜ˆ์™ธ', error: e); + if (mounted) { + setState(() { + _category2Options = []; + _isLoadingCategory2 = false; + }); + } + } + } + + Future _loadCategory3Options(String category1, String category2) async { + if (category1.isEmpty || category2.isEmpty) { + setState(() { + _category3Options = []; + }); + return; + } + + setState(() { + _isLoadingCategory3 = true; + }); + + try { + final result = await _lookupsService.getCategory3List(category1, category2); + result.fold( + (failure) { + DebugLogger.logError('์†Œ๋ถ„๋ฅ˜ ๋กœ๋”ฉ ์‹คํŒจ', error: failure.message); + if (mounted) { + setState(() { + _category3Options = []; + _isLoadingCategory3 = false; + }); + } + }, + (categories) { + if (mounted) { + setState(() { + _category3Options = categories; + _isLoadingCategory3 = false; + }); + } + }, + ); + } catch (e) { + DebugLogger.logError('์†Œ๋ถ„๋ฅ˜ ๋กœ๋”ฉ ์˜ˆ์™ธ', error: e); + if (mounted) { + setState(() { + _category3Options = []; + _isLoadingCategory3 = false; + }); + } + } + } + + void _onCategory1Changed(String? value) { + // ๋Œ€๋ถ„๋ฅ˜ ๋ณ€๊ฒฝ ์‹œ ์ค‘๋ถ„๋ฅ˜, ์†Œ๋ถ„๋ฅ˜ ์ดˆ๊ธฐํ™” + _category2Controller.clear(); + _category3Controller.clear(); + _category2Options.clear(); + _category3Options.clear(); + + // ์ƒˆ๋กœ์šด ๋Œ€๋ถ„๋ฅ˜์— ๋Œ€ํ•œ ์ค‘๋ถ„๋ฅ˜ ๋กœ๋”ฉ + if (value != null && value.isNotEmpty) { + _loadCategory2Options(value); + } + + // ๋ณ€๊ฒฝ ์•Œ๋ฆผ + widget.onChanged(value, null, null); + } + + void _onCategory2Changed(String? value) { + // ์ค‘๋ถ„๋ฅ˜ ๋ณ€๊ฒฝ ์‹œ ์†Œ๋ถ„๋ฅ˜ ์ดˆ๊ธฐํ™” + _category3Controller.clear(); + _category3Options.clear(); + + // ์ƒˆ๋กœ์šด ์ค‘๋ถ„๋ฅ˜์— ๋Œ€ํ•œ ์†Œ๋ถ„๋ฅ˜ ๋กœ๋”ฉ + final category1 = _category1Controller.text; + if (category1.isNotEmpty && value != null && value.isNotEmpty) { + _loadCategory3Options(category1, value); + } + + // ๋ณ€๊ฒฝ ์•Œ๋ฆผ + widget.onChanged(_category1Controller.text, value, null); + } + + void _onCategory3Changed(String? value) { + // ๋ณ€๊ฒฝ ์•Œ๋ฆผ + widget.onChanged( + _category1Controller.text, + _category2Controller.text, + value, + ); + } + + Widget _buildComboBox({ + required String labelText, + required TextEditingController controller, + required List options, + required void Function(String?) onChanged, + String? Function(String?)? validator, + bool isLoading = false, + }) { + // ๋“œ๋กญ๋‹ค์šด ์„ ํƒ ๊ฐ€๋Šฅํ•œ ๊ฐ’ (ํ˜„์žฌ ํ…์ŠคํŠธ๊ฐ€ ์˜ต์…˜์— ์žˆ์œผ๋ฉด ์„ ํƒ) + String? selectedValue; + if (controller.text.isNotEmpty && options.contains(controller.text)) { + selectedValue = controller.text; + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // ๋“œ๋กญ๋‹ค์šด ์„ ํƒ + if (options.isNotEmpty && !isLoading) + DropdownButtonFormField( + value: selectedValue, + items: options.map((option) { + return DropdownMenuItem( + value: option, + child: Text(option), + ); + }).toList(), + onChanged: widget.enabled ? (value) { + if (value != null) { + controller.text = value; + onChanged(value); + } + } : null, + decoration: InputDecoration( + labelText: '$labelText (์„ ํƒ)', + hintText: '$labelText๋ฅผ ์„ ํƒํ•˜์„ธ์š”', + border: const OutlineInputBorder(), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey.shade400), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Theme.of(context).primaryColor), + ), + ), + validator: validator, + ), + + // ๋กœ๋”ฉ ํ‘œ์‹œ + if (isLoading) + Container( + height: 56, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade400), + borderRadius: BorderRadius.circular(4), + ), + child: const Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ), + SizedBox(width: 8), + Text('๋กœ๋”ฉ ์ค‘...'), + ], + ), + ), + ), + + // ์˜ต์…˜์ด ์—†์„ ๋•Œ + if (options.isEmpty && !isLoading) + TextFormField( + controller: controller, + enabled: widget.enabled, + validator: validator, + onChanged: onChanged, + decoration: InputDecoration( + labelText: '$labelText (์ง์ ‘์ž…๋ ฅ)', + hintText: '$labelText๋ฅผ ์ง์ ‘ ์ž…๋ ฅํ•˜์„ธ์š”', + border: const OutlineInputBorder(), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey.shade400), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Theme.of(context).primaryColor), + ), + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + // ๋Œ€๋ถ„๋ฅ˜ + _buildComboBox( + labelText: '๋Œ€๋ถ„๋ฅ˜', + controller: _category1Controller, + options: _category1Options, + onChanged: _onCategory1Changed, + validator: widget.category1Validator, + ), + const SizedBox(height: 16), + + // ์ค‘๋ถ„๋ฅ˜ + _buildComboBox( + labelText: '์ค‘๋ถ„๋ฅ˜', + controller: _category2Controller, + options: _category2Options, + onChanged: _onCategory2Changed, + validator: widget.category2Validator, + isLoading: _isLoadingCategory2, + ), + const SizedBox(height: 16), + + // ์†Œ๋ถ„๋ฅ˜ + _buildComboBox( + labelText: '์†Œ๋ถ„๋ฅ˜', + controller: _category3Controller, + options: _category3Options, + onChanged: _onCategory3Changed, + validator: widget.category3Validator, + isLoading: _isLoadingCategory3, + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/data/models/equipment/equipment_dto.dart b/lib/data/models/equipment/equipment_dto.dart index 971eabb..686946f 100644 --- a/lib/data/models/equipment/equipment_dto.dart +++ b/lib/data/models/equipment/equipment_dto.dart @@ -7,24 +7,22 @@ part 'equipment_dto.g.dart'; class EquipmentDto with _$EquipmentDto { const factory EquipmentDto({ required int id, - @JsonKey(name: 'serial_number') required String serialNumber, - required String name, - String? category, - String? manufacturer, - String? model, + @JsonKey(name: 'equipment_number') required String equipmentNumber, + @JsonKey(name: 'serial_number') String? serialNumber, + String? category1, + String? category2, + String? category3, + required String manufacturer, + @JsonKey(name: 'model_name') String? modelName, + String? barcode, required String status, - @JsonKey(name: 'company_id') required int companyId, - @JsonKey(name: 'company_name') String? companyName, + @JsonKey(name: 'company_id') int? companyId, @JsonKey(name: 'warehouse_location_id') int? warehouseLocationId, - @JsonKey(name: 'warehouse_name') String? warehouseName, @JsonKey(name: 'purchase_date') String? purchaseDate, @JsonKey(name: 'purchase_price') double? purchasePrice, - @JsonKey(name: 'current_value') double? currentValue, - @JsonKey(name: 'warranty_expiry') String? warrantyExpiry, - @JsonKey(name: 'last_maintenance_date') String? lastMaintenanceDate, - @JsonKey(name: 'next_maintenance_date') String? nextMaintenanceDate, - Map? specifications, - String? notes, + @JsonKey(name: 'last_inspection_date') String? lastInspectionDate, + @JsonKey(name: 'next_inspection_date') String? nextInspectionDate, + String? remark, @JsonKey(name: 'created_at') DateTime? createdAt, @JsonKey(name: 'updated_at') DateTime? updatedAt, }) = _EquipmentDto; diff --git a/lib/data/models/equipment/equipment_dto.freezed.dart b/lib/data/models/equipment/equipment_dto.freezed.dart index be64fc0..e6ea8d5 100644 --- a/lib/data/models/equipment/equipment_dto.freezed.dart +++ b/lib/data/models/equipment/equipment_dto.freezed.dart @@ -21,36 +21,31 @@ EquipmentDto _$EquipmentDtoFromJson(Map json) { /// @nodoc mixin _$EquipmentDto { int get id => throw _privateConstructorUsedError; + @JsonKey(name: 'equipment_number') + String get equipmentNumber => throw _privateConstructorUsedError; @JsonKey(name: 'serial_number') - String get serialNumber => throw _privateConstructorUsedError; - String get name => throw _privateConstructorUsedError; - String? get category => throw _privateConstructorUsedError; - String? get manufacturer => throw _privateConstructorUsedError; - String? get model => throw _privateConstructorUsedError; + String? get serialNumber => throw _privateConstructorUsedError; + String? get category1 => throw _privateConstructorUsedError; + String? get category2 => throw _privateConstructorUsedError; + String? get category3 => throw _privateConstructorUsedError; + String get manufacturer => throw _privateConstructorUsedError; + @JsonKey(name: 'model_name') + String? get modelName => throw _privateConstructorUsedError; + String? get barcode => throw _privateConstructorUsedError; String get status => throw _privateConstructorUsedError; @JsonKey(name: 'company_id') - int get companyId => throw _privateConstructorUsedError; - @JsonKey(name: 'company_name') - String? get companyName => throw _privateConstructorUsedError; + int? get companyId => throw _privateConstructorUsedError; @JsonKey(name: 'warehouse_location_id') int? get warehouseLocationId => throw _privateConstructorUsedError; - @JsonKey(name: 'warehouse_name') - String? get warehouseName => throw _privateConstructorUsedError; @JsonKey(name: 'purchase_date') String? get purchaseDate => throw _privateConstructorUsedError; @JsonKey(name: 'purchase_price') double? get purchasePrice => throw _privateConstructorUsedError; - @JsonKey(name: 'current_value') - double? get currentValue => throw _privateConstructorUsedError; - @JsonKey(name: 'warranty_expiry') - String? get warrantyExpiry => throw _privateConstructorUsedError; - @JsonKey(name: 'last_maintenance_date') - String? get lastMaintenanceDate => throw _privateConstructorUsedError; - @JsonKey(name: 'next_maintenance_date') - String? get nextMaintenanceDate => throw _privateConstructorUsedError; - Map? get specifications => - throw _privateConstructorUsedError; - String? get notes => throw _privateConstructorUsedError; + @JsonKey(name: 'last_inspection_date') + String? get lastInspectionDate => throw _privateConstructorUsedError; + @JsonKey(name: 'next_inspection_date') + String? get nextInspectionDate => throw _privateConstructorUsedError; + String? get remark => throw _privateConstructorUsedError; @JsonKey(name: 'created_at') DateTime? get createdAt => throw _privateConstructorUsedError; @JsonKey(name: 'updated_at') @@ -74,24 +69,22 @@ abstract class $EquipmentDtoCopyWith<$Res> { @useResult $Res call( {int id, - @JsonKey(name: 'serial_number') String serialNumber, - String name, - String? category, - String? manufacturer, - String? model, + @JsonKey(name: 'equipment_number') String equipmentNumber, + @JsonKey(name: 'serial_number') String? serialNumber, + String? category1, + String? category2, + String? category3, + String manufacturer, + @JsonKey(name: 'model_name') String? modelName, + String? barcode, String status, - @JsonKey(name: 'company_id') int companyId, - @JsonKey(name: 'company_name') String? companyName, + @JsonKey(name: 'company_id') int? companyId, @JsonKey(name: 'warehouse_location_id') int? warehouseLocationId, - @JsonKey(name: 'warehouse_name') String? warehouseName, @JsonKey(name: 'purchase_date') String? purchaseDate, @JsonKey(name: 'purchase_price') double? purchasePrice, - @JsonKey(name: 'current_value') double? currentValue, - @JsonKey(name: 'warranty_expiry') String? warrantyExpiry, - @JsonKey(name: 'last_maintenance_date') String? lastMaintenanceDate, - @JsonKey(name: 'next_maintenance_date') String? nextMaintenanceDate, - Map? specifications, - String? notes, + @JsonKey(name: 'last_inspection_date') String? lastInspectionDate, + @JsonKey(name: 'next_inspection_date') String? nextInspectionDate, + String? remark, @JsonKey(name: 'created_at') DateTime? createdAt, @JsonKey(name: 'updated_at') DateTime? updatedAt}); } @@ -112,24 +105,22 @@ class _$EquipmentDtoCopyWithImpl<$Res, $Val extends EquipmentDto> @override $Res call({ Object? id = null, - Object? serialNumber = null, - Object? name = null, - Object? category = freezed, - Object? manufacturer = freezed, - Object? model = freezed, + Object? equipmentNumber = null, + Object? serialNumber = freezed, + Object? category1 = freezed, + Object? category2 = freezed, + Object? category3 = freezed, + Object? manufacturer = null, + Object? modelName = freezed, + Object? barcode = freezed, Object? status = null, - Object? companyId = null, - Object? companyName = freezed, + Object? companyId = freezed, Object? warehouseLocationId = freezed, - Object? warehouseName = freezed, Object? purchaseDate = freezed, Object? purchasePrice = freezed, - Object? currentValue = freezed, - Object? warrantyExpiry = freezed, - Object? lastMaintenanceDate = freezed, - Object? nextMaintenanceDate = freezed, - Object? specifications = freezed, - Object? notes = freezed, + Object? lastInspectionDate = freezed, + Object? nextInspectionDate = freezed, + Object? remark = freezed, Object? createdAt = freezed, Object? updatedAt = freezed, }) { @@ -138,46 +129,50 @@ class _$EquipmentDtoCopyWithImpl<$Res, $Val extends EquipmentDto> ? _value.id : id // ignore: cast_nullable_to_non_nullable as int, - serialNumber: null == serialNumber + equipmentNumber: null == equipmentNumber + ? _value.equipmentNumber + : equipmentNumber // ignore: cast_nullable_to_non_nullable + as String, + serialNumber: freezed == serialNumber ? _value.serialNumber : serialNumber // ignore: cast_nullable_to_non_nullable - as String, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - category: freezed == category - ? _value.category - : category // ignore: cast_nullable_to_non_nullable as String?, - manufacturer: freezed == manufacturer + category1: freezed == category1 + ? _value.category1 + : category1 // ignore: cast_nullable_to_non_nullable + as String?, + category2: freezed == category2 + ? _value.category2 + : category2 // ignore: cast_nullable_to_non_nullable + as String?, + category3: freezed == category3 + ? _value.category3 + : category3 // ignore: cast_nullable_to_non_nullable + as String?, + manufacturer: null == manufacturer ? _value.manufacturer : manufacturer // ignore: cast_nullable_to_non_nullable + as String, + modelName: freezed == modelName + ? _value.modelName + : modelName // ignore: cast_nullable_to_non_nullable as String?, - model: freezed == model - ? _value.model - : model // ignore: cast_nullable_to_non_nullable + barcode: freezed == barcode + ? _value.barcode + : barcode // ignore: cast_nullable_to_non_nullable as String?, status: null == status ? _value.status : status // ignore: cast_nullable_to_non_nullable as String, - companyId: null == companyId + companyId: freezed == companyId ? _value.companyId : companyId // ignore: cast_nullable_to_non_nullable - as int, - companyName: freezed == companyName - ? _value.companyName - : companyName // ignore: cast_nullable_to_non_nullable - as String?, + as int?, warehouseLocationId: freezed == warehouseLocationId ? _value.warehouseLocationId : warehouseLocationId // ignore: cast_nullable_to_non_nullable as int?, - warehouseName: freezed == warehouseName - ? _value.warehouseName - : warehouseName // ignore: cast_nullable_to_non_nullable - as String?, purchaseDate: freezed == purchaseDate ? _value.purchaseDate : purchaseDate // ignore: cast_nullable_to_non_nullable @@ -186,29 +181,17 @@ class _$EquipmentDtoCopyWithImpl<$Res, $Val extends EquipmentDto> ? _value.purchasePrice : purchasePrice // ignore: cast_nullable_to_non_nullable as double?, - currentValue: freezed == currentValue - ? _value.currentValue - : currentValue // ignore: cast_nullable_to_non_nullable - as double?, - warrantyExpiry: freezed == warrantyExpiry - ? _value.warrantyExpiry - : warrantyExpiry // ignore: cast_nullable_to_non_nullable + lastInspectionDate: freezed == lastInspectionDate + ? _value.lastInspectionDate + : lastInspectionDate // ignore: cast_nullable_to_non_nullable as String?, - lastMaintenanceDate: freezed == lastMaintenanceDate - ? _value.lastMaintenanceDate - : lastMaintenanceDate // ignore: cast_nullable_to_non_nullable + nextInspectionDate: freezed == nextInspectionDate + ? _value.nextInspectionDate + : nextInspectionDate // ignore: cast_nullable_to_non_nullable as String?, - nextMaintenanceDate: freezed == nextMaintenanceDate - ? _value.nextMaintenanceDate - : nextMaintenanceDate // ignore: cast_nullable_to_non_nullable - as String?, - specifications: freezed == specifications - ? _value.specifications - : specifications // ignore: cast_nullable_to_non_nullable - as Map?, - notes: freezed == notes - ? _value.notes - : notes // ignore: cast_nullable_to_non_nullable + remark: freezed == remark + ? _value.remark + : remark // ignore: cast_nullable_to_non_nullable as String?, createdAt: freezed == createdAt ? _value.createdAt @@ -232,24 +215,22 @@ abstract class _$$EquipmentDtoImplCopyWith<$Res> @useResult $Res call( {int id, - @JsonKey(name: 'serial_number') String serialNumber, - String name, - String? category, - String? manufacturer, - String? model, + @JsonKey(name: 'equipment_number') String equipmentNumber, + @JsonKey(name: 'serial_number') String? serialNumber, + String? category1, + String? category2, + String? category3, + String manufacturer, + @JsonKey(name: 'model_name') String? modelName, + String? barcode, String status, - @JsonKey(name: 'company_id') int companyId, - @JsonKey(name: 'company_name') String? companyName, + @JsonKey(name: 'company_id') int? companyId, @JsonKey(name: 'warehouse_location_id') int? warehouseLocationId, - @JsonKey(name: 'warehouse_name') String? warehouseName, @JsonKey(name: 'purchase_date') String? purchaseDate, @JsonKey(name: 'purchase_price') double? purchasePrice, - @JsonKey(name: 'current_value') double? currentValue, - @JsonKey(name: 'warranty_expiry') String? warrantyExpiry, - @JsonKey(name: 'last_maintenance_date') String? lastMaintenanceDate, - @JsonKey(name: 'next_maintenance_date') String? nextMaintenanceDate, - Map? specifications, - String? notes, + @JsonKey(name: 'last_inspection_date') String? lastInspectionDate, + @JsonKey(name: 'next_inspection_date') String? nextInspectionDate, + String? remark, @JsonKey(name: 'created_at') DateTime? createdAt, @JsonKey(name: 'updated_at') DateTime? updatedAt}); } @@ -268,24 +249,22 @@ class __$$EquipmentDtoImplCopyWithImpl<$Res> @override $Res call({ Object? id = null, - Object? serialNumber = null, - Object? name = null, - Object? category = freezed, - Object? manufacturer = freezed, - Object? model = freezed, + Object? equipmentNumber = null, + Object? serialNumber = freezed, + Object? category1 = freezed, + Object? category2 = freezed, + Object? category3 = freezed, + Object? manufacturer = null, + Object? modelName = freezed, + Object? barcode = freezed, Object? status = null, - Object? companyId = null, - Object? companyName = freezed, + Object? companyId = freezed, Object? warehouseLocationId = freezed, - Object? warehouseName = freezed, Object? purchaseDate = freezed, Object? purchasePrice = freezed, - Object? currentValue = freezed, - Object? warrantyExpiry = freezed, - Object? lastMaintenanceDate = freezed, - Object? nextMaintenanceDate = freezed, - Object? specifications = freezed, - Object? notes = freezed, + Object? lastInspectionDate = freezed, + Object? nextInspectionDate = freezed, + Object? remark = freezed, Object? createdAt = freezed, Object? updatedAt = freezed, }) { @@ -294,46 +273,50 @@ class __$$EquipmentDtoImplCopyWithImpl<$Res> ? _value.id : id // ignore: cast_nullable_to_non_nullable as int, - serialNumber: null == serialNumber + equipmentNumber: null == equipmentNumber + ? _value.equipmentNumber + : equipmentNumber // ignore: cast_nullable_to_non_nullable + as String, + serialNumber: freezed == serialNumber ? _value.serialNumber : serialNumber // ignore: cast_nullable_to_non_nullable - as String, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - category: freezed == category - ? _value.category - : category // ignore: cast_nullable_to_non_nullable as String?, - manufacturer: freezed == manufacturer + category1: freezed == category1 + ? _value.category1 + : category1 // ignore: cast_nullable_to_non_nullable + as String?, + category2: freezed == category2 + ? _value.category2 + : category2 // ignore: cast_nullable_to_non_nullable + as String?, + category3: freezed == category3 + ? _value.category3 + : category3 // ignore: cast_nullable_to_non_nullable + as String?, + manufacturer: null == manufacturer ? _value.manufacturer : manufacturer // ignore: cast_nullable_to_non_nullable + as String, + modelName: freezed == modelName + ? _value.modelName + : modelName // ignore: cast_nullable_to_non_nullable as String?, - model: freezed == model - ? _value.model - : model // ignore: cast_nullable_to_non_nullable + barcode: freezed == barcode + ? _value.barcode + : barcode // ignore: cast_nullable_to_non_nullable as String?, status: null == status ? _value.status : status // ignore: cast_nullable_to_non_nullable as String, - companyId: null == companyId + companyId: freezed == companyId ? _value.companyId : companyId // ignore: cast_nullable_to_non_nullable - as int, - companyName: freezed == companyName - ? _value.companyName - : companyName // ignore: cast_nullable_to_non_nullable - as String?, + as int?, warehouseLocationId: freezed == warehouseLocationId ? _value.warehouseLocationId : warehouseLocationId // ignore: cast_nullable_to_non_nullable as int?, - warehouseName: freezed == warehouseName - ? _value.warehouseName - : warehouseName // ignore: cast_nullable_to_non_nullable - as String?, purchaseDate: freezed == purchaseDate ? _value.purchaseDate : purchaseDate // ignore: cast_nullable_to_non_nullable @@ -342,29 +325,17 @@ class __$$EquipmentDtoImplCopyWithImpl<$Res> ? _value.purchasePrice : purchasePrice // ignore: cast_nullable_to_non_nullable as double?, - currentValue: freezed == currentValue - ? _value.currentValue - : currentValue // ignore: cast_nullable_to_non_nullable - as double?, - warrantyExpiry: freezed == warrantyExpiry - ? _value.warrantyExpiry - : warrantyExpiry // ignore: cast_nullable_to_non_nullable + lastInspectionDate: freezed == lastInspectionDate + ? _value.lastInspectionDate + : lastInspectionDate // ignore: cast_nullable_to_non_nullable as String?, - lastMaintenanceDate: freezed == lastMaintenanceDate - ? _value.lastMaintenanceDate - : lastMaintenanceDate // ignore: cast_nullable_to_non_nullable + nextInspectionDate: freezed == nextInspectionDate + ? _value.nextInspectionDate + : nextInspectionDate // ignore: cast_nullable_to_non_nullable as String?, - nextMaintenanceDate: freezed == nextMaintenanceDate - ? _value.nextMaintenanceDate - : nextMaintenanceDate // ignore: cast_nullable_to_non_nullable - as String?, - specifications: freezed == specifications - ? _value._specifications - : specifications // ignore: cast_nullable_to_non_nullable - as Map?, - notes: freezed == notes - ? _value.notes - : notes // ignore: cast_nullable_to_non_nullable + remark: freezed == remark + ? _value.remark + : remark // ignore: cast_nullable_to_non_nullable as String?, createdAt: freezed == createdAt ? _value.createdAt @@ -383,27 +354,24 @@ class __$$EquipmentDtoImplCopyWithImpl<$Res> class _$EquipmentDtoImpl implements _EquipmentDto { const _$EquipmentDtoImpl( {required this.id, - @JsonKey(name: 'serial_number') required this.serialNumber, - required this.name, - this.category, - this.manufacturer, - this.model, + @JsonKey(name: 'equipment_number') required this.equipmentNumber, + @JsonKey(name: 'serial_number') this.serialNumber, + this.category1, + this.category2, + this.category3, + required this.manufacturer, + @JsonKey(name: 'model_name') this.modelName, + this.barcode, required this.status, - @JsonKey(name: 'company_id') required this.companyId, - @JsonKey(name: 'company_name') this.companyName, + @JsonKey(name: 'company_id') this.companyId, @JsonKey(name: 'warehouse_location_id') this.warehouseLocationId, - @JsonKey(name: 'warehouse_name') this.warehouseName, @JsonKey(name: 'purchase_date') this.purchaseDate, @JsonKey(name: 'purchase_price') this.purchasePrice, - @JsonKey(name: 'current_value') this.currentValue, - @JsonKey(name: 'warranty_expiry') this.warrantyExpiry, - @JsonKey(name: 'last_maintenance_date') this.lastMaintenanceDate, - @JsonKey(name: 'next_maintenance_date') this.nextMaintenanceDate, - final Map? specifications, - this.notes, + @JsonKey(name: 'last_inspection_date') this.lastInspectionDate, + @JsonKey(name: 'next_inspection_date') this.nextInspectionDate, + this.remark, @JsonKey(name: 'created_at') this.createdAt, - @JsonKey(name: 'updated_at') this.updatedAt}) - : _specifications = specifications; + @JsonKey(name: 'updated_at') this.updatedAt}); factory _$EquipmentDtoImpl.fromJson(Map json) => _$$EquipmentDtoImplFromJson(json); @@ -411,60 +379,46 @@ class _$EquipmentDtoImpl implements _EquipmentDto { @override final int id; @override + @JsonKey(name: 'equipment_number') + final String equipmentNumber; + @override @JsonKey(name: 'serial_number') - final String serialNumber; + final String? serialNumber; @override - final String name; + final String? category1; @override - final String? category; + final String? category2; @override - final String? manufacturer; + final String? category3; @override - final String? model; + final String manufacturer; + @override + @JsonKey(name: 'model_name') + final String? modelName; + @override + final String? barcode; @override final String status; @override @JsonKey(name: 'company_id') - final int companyId; - @override - @JsonKey(name: 'company_name') - final String? companyName; + final int? companyId; @override @JsonKey(name: 'warehouse_location_id') final int? warehouseLocationId; @override - @JsonKey(name: 'warehouse_name') - final String? warehouseName; - @override @JsonKey(name: 'purchase_date') final String? purchaseDate; @override @JsonKey(name: 'purchase_price') final double? purchasePrice; @override - @JsonKey(name: 'current_value') - final double? currentValue; + @JsonKey(name: 'last_inspection_date') + final String? lastInspectionDate; @override - @JsonKey(name: 'warranty_expiry') - final String? warrantyExpiry; + @JsonKey(name: 'next_inspection_date') + final String? nextInspectionDate; @override - @JsonKey(name: 'last_maintenance_date') - final String? lastMaintenanceDate; - @override - @JsonKey(name: 'next_maintenance_date') - final String? nextMaintenanceDate; - final Map? _specifications; - @override - Map? get specifications { - final value = _specifications; - if (value == null) return null; - if (_specifications is EqualUnmodifiableMapView) return _specifications; - // ignore: implicit_dynamic_type - return EqualUnmodifiableMapView(value); - } - - @override - final String? notes; + final String? remark; @override @JsonKey(name: 'created_at') final DateTime? createdAt; @@ -474,7 +428,7 @@ class _$EquipmentDtoImpl implements _EquipmentDto { @override String toString() { - return 'EquipmentDto(id: $id, serialNumber: $serialNumber, name: $name, category: $category, manufacturer: $manufacturer, model: $model, status: $status, companyId: $companyId, companyName: $companyName, warehouseLocationId: $warehouseLocationId, warehouseName: $warehouseName, purchaseDate: $purchaseDate, purchasePrice: $purchasePrice, currentValue: $currentValue, warrantyExpiry: $warrantyExpiry, lastMaintenanceDate: $lastMaintenanceDate, nextMaintenanceDate: $nextMaintenanceDate, specifications: $specifications, notes: $notes, createdAt: $createdAt, updatedAt: $updatedAt)'; + return 'EquipmentDto(id: $id, equipmentNumber: $equipmentNumber, serialNumber: $serialNumber, category1: $category1, category2: $category2, category3: $category3, manufacturer: $manufacturer, modelName: $modelName, barcode: $barcode, status: $status, companyId: $companyId, warehouseLocationId: $warehouseLocationId, purchaseDate: $purchaseDate, purchasePrice: $purchasePrice, lastInspectionDate: $lastInspectionDate, nextInspectionDate: $nextInspectionDate, remark: $remark, createdAt: $createdAt, updatedAt: $updatedAt)'; } @override @@ -483,38 +437,35 @@ class _$EquipmentDtoImpl implements _EquipmentDto { (other.runtimeType == runtimeType && other is _$EquipmentDtoImpl && (identical(other.id, id) || other.id == id) && + (identical(other.equipmentNumber, equipmentNumber) || + other.equipmentNumber == equipmentNumber) && (identical(other.serialNumber, serialNumber) || other.serialNumber == serialNumber) && - (identical(other.name, name) || other.name == name) && - (identical(other.category, category) || - other.category == category) && + (identical(other.category1, category1) || + other.category1 == category1) && + (identical(other.category2, category2) || + other.category2 == category2) && + (identical(other.category3, category3) || + other.category3 == category3) && (identical(other.manufacturer, manufacturer) || other.manufacturer == manufacturer) && - (identical(other.model, model) || other.model == model) && + (identical(other.modelName, modelName) || + other.modelName == modelName) && + (identical(other.barcode, barcode) || other.barcode == barcode) && (identical(other.status, status) || other.status == status) && (identical(other.companyId, companyId) || other.companyId == companyId) && - (identical(other.companyName, companyName) || - other.companyName == companyName) && (identical(other.warehouseLocationId, warehouseLocationId) || other.warehouseLocationId == warehouseLocationId) && - (identical(other.warehouseName, warehouseName) || - other.warehouseName == warehouseName) && (identical(other.purchaseDate, purchaseDate) || other.purchaseDate == purchaseDate) && (identical(other.purchasePrice, purchasePrice) || other.purchasePrice == purchasePrice) && - (identical(other.currentValue, currentValue) || - other.currentValue == currentValue) && - (identical(other.warrantyExpiry, warrantyExpiry) || - other.warrantyExpiry == warrantyExpiry) && - (identical(other.lastMaintenanceDate, lastMaintenanceDate) || - other.lastMaintenanceDate == lastMaintenanceDate) && - (identical(other.nextMaintenanceDate, nextMaintenanceDate) || - other.nextMaintenanceDate == nextMaintenanceDate) && - const DeepCollectionEquality() - .equals(other._specifications, _specifications) && - (identical(other.notes, notes) || other.notes == notes) && + (identical(other.lastInspectionDate, lastInspectionDate) || + other.lastInspectionDate == lastInspectionDate) && + (identical(other.nextInspectionDate, nextInspectionDate) || + other.nextInspectionDate == nextInspectionDate) && + (identical(other.remark, remark) || other.remark == remark) && (identical(other.createdAt, createdAt) || other.createdAt == createdAt) && (identical(other.updatedAt, updatedAt) || @@ -526,24 +477,22 @@ class _$EquipmentDtoImpl implements _EquipmentDto { int get hashCode => Object.hashAll([ runtimeType, id, + equipmentNumber, serialNumber, - name, - category, + category1, + category2, + category3, manufacturer, - model, + modelName, + barcode, status, companyId, - companyName, warehouseLocationId, - warehouseName, purchaseDate, purchasePrice, - currentValue, - warrantyExpiry, - lastMaintenanceDate, - nextMaintenanceDate, - const DeepCollectionEquality().hash(_specifications), - notes, + lastInspectionDate, + nextInspectionDate, + remark, createdAt, updatedAt ]); @@ -567,24 +516,22 @@ class _$EquipmentDtoImpl implements _EquipmentDto { abstract class _EquipmentDto implements EquipmentDto { const factory _EquipmentDto( {required final int id, - @JsonKey(name: 'serial_number') required final String serialNumber, - required final String name, - final String? category, - final String? manufacturer, - final String? model, + @JsonKey(name: 'equipment_number') required final String equipmentNumber, + @JsonKey(name: 'serial_number') final String? serialNumber, + final String? category1, + final String? category2, + final String? category3, + required final String manufacturer, + @JsonKey(name: 'model_name') final String? modelName, + final String? barcode, required final String status, - @JsonKey(name: 'company_id') required final int companyId, - @JsonKey(name: 'company_name') final String? companyName, + @JsonKey(name: 'company_id') final int? companyId, @JsonKey(name: 'warehouse_location_id') final int? warehouseLocationId, - @JsonKey(name: 'warehouse_name') final String? warehouseName, @JsonKey(name: 'purchase_date') final String? purchaseDate, @JsonKey(name: 'purchase_price') final double? purchasePrice, - @JsonKey(name: 'current_value') final double? currentValue, - @JsonKey(name: 'warranty_expiry') final String? warrantyExpiry, - @JsonKey(name: 'last_maintenance_date') final String? lastMaintenanceDate, - @JsonKey(name: 'next_maintenance_date') final String? nextMaintenanceDate, - final Map? specifications, - final String? notes, + @JsonKey(name: 'last_inspection_date') final String? lastInspectionDate, + @JsonKey(name: 'next_inspection_date') final String? nextInspectionDate, + final String? remark, @JsonKey(name: 'created_at') final DateTime? createdAt, @JsonKey(name: 'updated_at') final DateTime? updatedAt}) = _$EquipmentDtoImpl; @@ -595,52 +542,46 @@ abstract class _EquipmentDto implements EquipmentDto { @override int get id; @override + @JsonKey(name: 'equipment_number') + String get equipmentNumber; + @override @JsonKey(name: 'serial_number') - String get serialNumber; + String? get serialNumber; @override - String get name; + String? get category1; @override - String? get category; + String? get category2; @override - String? get manufacturer; + String? get category3; @override - String? get model; + String get manufacturer; + @override + @JsonKey(name: 'model_name') + String? get modelName; + @override + String? get barcode; @override String get status; @override @JsonKey(name: 'company_id') - int get companyId; - @override - @JsonKey(name: 'company_name') - String? get companyName; + int? get companyId; @override @JsonKey(name: 'warehouse_location_id') int? get warehouseLocationId; @override - @JsonKey(name: 'warehouse_name') - String? get warehouseName; - @override @JsonKey(name: 'purchase_date') String? get purchaseDate; @override @JsonKey(name: 'purchase_price') double? get purchasePrice; @override - @JsonKey(name: 'current_value') - double? get currentValue; + @JsonKey(name: 'last_inspection_date') + String? get lastInspectionDate; @override - @JsonKey(name: 'warranty_expiry') - String? get warrantyExpiry; + @JsonKey(name: 'next_inspection_date') + String? get nextInspectionDate; @override - @JsonKey(name: 'last_maintenance_date') - String? get lastMaintenanceDate; - @override - @JsonKey(name: 'next_maintenance_date') - String? get nextMaintenanceDate; - @override - Map? get specifications; - @override - String? get notes; + String? get remark; @override @JsonKey(name: 'created_at') DateTime? get createdAt; diff --git a/lib/data/models/equipment/equipment_dto.g.dart b/lib/data/models/equipment/equipment_dto.g.dart index b9d267a..e101375 100644 --- a/lib/data/models/equipment/equipment_dto.g.dart +++ b/lib/data/models/equipment/equipment_dto.g.dart @@ -9,24 +9,22 @@ part of 'equipment_dto.dart'; _$EquipmentDtoImpl _$$EquipmentDtoImplFromJson(Map json) => _$EquipmentDtoImpl( id: (json['id'] as num).toInt(), - serialNumber: json['serial_number'] as String, - name: json['name'] as String, - category: json['category'] as String?, - manufacturer: json['manufacturer'] as String?, - model: json['model'] as String?, + equipmentNumber: json['equipment_number'] as String, + serialNumber: json['serial_number'] as String?, + category1: json['category1'] as String?, + category2: json['category2'] as String?, + category3: json['category3'] as String?, + manufacturer: json['manufacturer'] as String, + modelName: json['model_name'] as String?, + barcode: json['barcode'] as String?, status: json['status'] as String, - companyId: (json['company_id'] as num).toInt(), - companyName: json['company_name'] as String?, + companyId: (json['company_id'] as num?)?.toInt(), warehouseLocationId: (json['warehouse_location_id'] as num?)?.toInt(), - warehouseName: json['warehouse_name'] as String?, purchaseDate: json['purchase_date'] as String?, purchasePrice: (json['purchase_price'] as num?)?.toDouble(), - currentValue: (json['current_value'] as num?)?.toDouble(), - warrantyExpiry: json['warranty_expiry'] as String?, - lastMaintenanceDate: json['last_maintenance_date'] as String?, - nextMaintenanceDate: json['next_maintenance_date'] as String?, - specifications: json['specifications'] as Map?, - notes: json['notes'] as String?, + lastInspectionDate: json['last_inspection_date'] as String?, + nextInspectionDate: json['next_inspection_date'] as String?, + remark: json['remark'] as String?, createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), @@ -38,24 +36,22 @@ _$EquipmentDtoImpl _$$EquipmentDtoImplFromJson(Map json) => Map _$$EquipmentDtoImplToJson(_$EquipmentDtoImpl instance) => { 'id': instance.id, + 'equipment_number': instance.equipmentNumber, 'serial_number': instance.serialNumber, - 'name': instance.name, - 'category': instance.category, + 'category1': instance.category1, + 'category2': instance.category2, + 'category3': instance.category3, 'manufacturer': instance.manufacturer, - 'model': instance.model, + 'model_name': instance.modelName, + 'barcode': instance.barcode, 'status': instance.status, 'company_id': instance.companyId, - 'company_name': instance.companyName, 'warehouse_location_id': instance.warehouseLocationId, - 'warehouse_name': instance.warehouseName, 'purchase_date': instance.purchaseDate, 'purchase_price': instance.purchasePrice, - 'current_value': instance.currentValue, - 'warranty_expiry': instance.warrantyExpiry, - 'last_maintenance_date': instance.lastMaintenanceDate, - 'next_maintenance_date': instance.nextMaintenanceDate, - 'specifications': instance.specifications, - 'notes': instance.notes, + 'last_inspection_date': instance.lastInspectionDate, + 'next_inspection_date': instance.nextInspectionDate, + 'remark': instance.remark, 'created_at': instance.createdAt?.toIso8601String(), 'updated_at': instance.updatedAt?.toIso8601String(), }; diff --git a/lib/data/models/lookups/lookup_data.dart b/lib/data/models/lookups/lookup_data.dart index bb88d82..411fa3f 100644 --- a/lib/data/models/lookups/lookup_data.dart +++ b/lib/data/models/lookups/lookup_data.dart @@ -9,8 +9,10 @@ class LookupData with _$LookupData { const factory LookupData({ @JsonKey(name: 'manufacturers', defaultValue: []) required List manufacturers, @JsonKey(name: 'equipment_names', defaultValue: []) required List equipmentNames, - @JsonKey(name: 'equipment_categories', defaultValue: []) required List equipmentCategories, + @JsonKey(name: 'equipment_categories', defaultValue: []) required List equipmentCategories, @JsonKey(name: 'equipment_statuses', defaultValue: []) required List equipmentStatuses, + @JsonKey(name: 'companies', defaultValue: []) required List companies, + @JsonKey(name: 'warehouses', defaultValue: []) required List warehouses, }) = _LookupData; factory LookupData.fromJson(Map json) => @@ -21,7 +23,7 @@ class LookupData with _$LookupData { @freezed class LookupItem with _$LookupItem { const factory LookupItem({ - required int id, + int? id, required String name, }) = _LookupItem; @@ -33,7 +35,7 @@ class LookupItem with _$LookupItem { @freezed class EquipmentNameItem with _$EquipmentNameItem { const factory EquipmentNameItem({ - required int id, + int? id, required String name, @JsonKey(name: 'model_number') String? modelNumber, }) = _EquipmentNameItem; @@ -42,7 +44,20 @@ class EquipmentNameItem with _$EquipmentNameItem { _$EquipmentNameItemFromJson(json); } -/// ์นดํ…Œ๊ณ ๋ฆฌ Lookup ์•„์ดํ…œ +/// ์นดํ…Œ๊ณ ๋ฆฌ ์กฐํ•ฉ Lookup ์•„์ดํ…œ (๋ฐฑ์—”๋“œ API ์‹ค์ œ ๊ตฌ์กฐ) +@freezed +class CategoryCombinationItem with _$CategoryCombinationItem { + const factory CategoryCombinationItem({ + required String category1, + required String category2, + required String category3, + }) = _CategoryCombinationItem; + + factory CategoryCombinationItem.fromJson(Map json) => + _$CategoryCombinationItemFromJson(json); +} + +/// ๊ฐœ๋ณ„ ์นดํ…Œ๊ณ ๋ฆฌ ์•„์ดํ…œ (UI์šฉ) @freezed class CategoryItem with _$CategoryItem { const factory CategoryItem({ diff --git a/lib/data/models/lookups/lookup_data.freezed.dart b/lib/data/models/lookups/lookup_data.freezed.dart index 0904171..81b11ea 100644 --- a/lib/data/models/lookups/lookup_data.freezed.dart +++ b/lib/data/models/lookups/lookup_data.freezed.dart @@ -26,10 +26,14 @@ mixin _$LookupData { List get equipmentNames => throw _privateConstructorUsedError; @JsonKey(name: 'equipment_categories', defaultValue: []) - List get equipmentCategories => + List get equipmentCategories => throw _privateConstructorUsedError; @JsonKey(name: 'equipment_statuses', defaultValue: []) List get equipmentStatuses => throw _privateConstructorUsedError; + @JsonKey(name: 'companies', defaultValue: []) + List get companies => throw _privateConstructorUsedError; + @JsonKey(name: 'warehouses', defaultValue: []) + List get warehouses => throw _privateConstructorUsedError; /// Serializes this LookupData to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -53,9 +57,12 @@ abstract class $LookupDataCopyWith<$Res> { @JsonKey(name: 'equipment_names', defaultValue: []) List equipmentNames, @JsonKey(name: 'equipment_categories', defaultValue: []) - List equipmentCategories, + List equipmentCategories, @JsonKey(name: 'equipment_statuses', defaultValue: []) - List equipmentStatuses}); + List equipmentStatuses, + @JsonKey(name: 'companies', defaultValue: []) List companies, + @JsonKey(name: 'warehouses', defaultValue: []) + List warehouses}); } /// @nodoc @@ -77,6 +84,8 @@ class _$LookupDataCopyWithImpl<$Res, $Val extends LookupData> Object? equipmentNames = null, Object? equipmentCategories = null, Object? equipmentStatuses = null, + Object? companies = null, + Object? warehouses = null, }) { return _then(_value.copyWith( manufacturers: null == manufacturers @@ -90,11 +99,19 @@ class _$LookupDataCopyWithImpl<$Res, $Val extends LookupData> equipmentCategories: null == equipmentCategories ? _value.equipmentCategories : equipmentCategories // ignore: cast_nullable_to_non_nullable - as List, + as List, equipmentStatuses: null == equipmentStatuses ? _value.equipmentStatuses : equipmentStatuses // ignore: cast_nullable_to_non_nullable as List, + companies: null == companies + ? _value.companies + : companies // ignore: cast_nullable_to_non_nullable + as List, + warehouses: null == warehouses + ? _value.warehouses + : warehouses // ignore: cast_nullable_to_non_nullable + as List, ) as $Val); } } @@ -113,9 +130,12 @@ abstract class _$$LookupDataImplCopyWith<$Res> @JsonKey(name: 'equipment_names', defaultValue: []) List equipmentNames, @JsonKey(name: 'equipment_categories', defaultValue: []) - List equipmentCategories, + List equipmentCategories, @JsonKey(name: 'equipment_statuses', defaultValue: []) - List equipmentStatuses}); + List equipmentStatuses, + @JsonKey(name: 'companies', defaultValue: []) List companies, + @JsonKey(name: 'warehouses', defaultValue: []) + List warehouses}); } /// @nodoc @@ -135,6 +155,8 @@ class __$$LookupDataImplCopyWithImpl<$Res> Object? equipmentNames = null, Object? equipmentCategories = null, Object? equipmentStatuses = null, + Object? companies = null, + Object? warehouses = null, }) { return _then(_$LookupDataImpl( manufacturers: null == manufacturers @@ -148,11 +170,19 @@ class __$$LookupDataImplCopyWithImpl<$Res> equipmentCategories: null == equipmentCategories ? _value._equipmentCategories : equipmentCategories // ignore: cast_nullable_to_non_nullable - as List, + as List, equipmentStatuses: null == equipmentStatuses ? _value._equipmentStatuses : equipmentStatuses // ignore: cast_nullable_to_non_nullable as List, + companies: null == companies + ? _value._companies + : companies // ignore: cast_nullable_to_non_nullable + as List, + warehouses: null == warehouses + ? _value._warehouses + : warehouses // ignore: cast_nullable_to_non_nullable + as List, )); } } @@ -166,13 +196,19 @@ class _$LookupDataImpl implements _LookupData { @JsonKey(name: 'equipment_names', defaultValue: []) required final List equipmentNames, @JsonKey(name: 'equipment_categories', defaultValue: []) - required final List equipmentCategories, + required final List equipmentCategories, @JsonKey(name: 'equipment_statuses', defaultValue: []) - required final List equipmentStatuses}) + required final List equipmentStatuses, + @JsonKey(name: 'companies', defaultValue: []) + required final List companies, + @JsonKey(name: 'warehouses', defaultValue: []) + required final List warehouses}) : _manufacturers = manufacturers, _equipmentNames = equipmentNames, _equipmentCategories = equipmentCategories, - _equipmentStatuses = equipmentStatuses; + _equipmentStatuses = equipmentStatuses, + _companies = companies, + _warehouses = warehouses; factory _$LookupDataImpl.fromJson(Map json) => _$$LookupDataImplFromJson(json); @@ -195,10 +231,10 @@ class _$LookupDataImpl implements _LookupData { return EqualUnmodifiableListView(_equipmentNames); } - final List _equipmentCategories; + final List _equipmentCategories; @override @JsonKey(name: 'equipment_categories', defaultValue: []) - List get equipmentCategories { + List get equipmentCategories { if (_equipmentCategories is EqualUnmodifiableListView) return _equipmentCategories; // ignore: implicit_dynamic_type @@ -215,9 +251,27 @@ class _$LookupDataImpl implements _LookupData { return EqualUnmodifiableListView(_equipmentStatuses); } + final List _companies; + @override + @JsonKey(name: 'companies', defaultValue: []) + List get companies { + if (_companies is EqualUnmodifiableListView) return _companies; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_companies); + } + + final List _warehouses; + @override + @JsonKey(name: 'warehouses', defaultValue: []) + List get warehouses { + if (_warehouses is EqualUnmodifiableListView) return _warehouses; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_warehouses); + } + @override String toString() { - return 'LookupData(manufacturers: $manufacturers, equipmentNames: $equipmentNames, equipmentCategories: $equipmentCategories, equipmentStatuses: $equipmentStatuses)'; + return 'LookupData(manufacturers: $manufacturers, equipmentNames: $equipmentNames, equipmentCategories: $equipmentCategories, equipmentStatuses: $equipmentStatuses, companies: $companies, warehouses: $warehouses)'; } @override @@ -232,7 +286,11 @@ class _$LookupDataImpl implements _LookupData { const DeepCollectionEquality() .equals(other._equipmentCategories, _equipmentCategories) && const DeepCollectionEquality() - .equals(other._equipmentStatuses, _equipmentStatuses)); + .equals(other._equipmentStatuses, _equipmentStatuses) && + const DeepCollectionEquality() + .equals(other._companies, _companies) && + const DeepCollectionEquality() + .equals(other._warehouses, _warehouses)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -242,7 +300,9 @@ class _$LookupDataImpl implements _LookupData { const DeepCollectionEquality().hash(_manufacturers), const DeepCollectionEquality().hash(_equipmentNames), const DeepCollectionEquality().hash(_equipmentCategories), - const DeepCollectionEquality().hash(_equipmentStatuses)); + const DeepCollectionEquality().hash(_equipmentStatuses), + const DeepCollectionEquality().hash(_companies), + const DeepCollectionEquality().hash(_warehouses)); /// Create a copy of LookupData /// with the given fields replaced by the non-null parameter values. @@ -267,9 +327,13 @@ abstract class _LookupData implements LookupData { @JsonKey(name: 'equipment_names', defaultValue: []) required final List equipmentNames, @JsonKey(name: 'equipment_categories', defaultValue: []) - required final List equipmentCategories, + required final List equipmentCategories, @JsonKey(name: 'equipment_statuses', defaultValue: []) - required final List equipmentStatuses}) = _$LookupDataImpl; + required final List equipmentStatuses, + @JsonKey(name: 'companies', defaultValue: []) + required final List companies, + @JsonKey(name: 'warehouses', defaultValue: []) + required final List warehouses}) = _$LookupDataImpl; factory _LookupData.fromJson(Map json) = _$LookupDataImpl.fromJson; @@ -282,10 +346,16 @@ abstract class _LookupData implements LookupData { List get equipmentNames; @override @JsonKey(name: 'equipment_categories', defaultValue: []) - List get equipmentCategories; + List get equipmentCategories; @override @JsonKey(name: 'equipment_statuses', defaultValue: []) List get equipmentStatuses; + @override + @JsonKey(name: 'companies', defaultValue: []) + List get companies; + @override + @JsonKey(name: 'warehouses', defaultValue: []) + List get warehouses; /// Create a copy of LookupData /// with the given fields replaced by the non-null parameter values. @@ -301,7 +371,7 @@ LookupItem _$LookupItemFromJson(Map json) { /// @nodoc mixin _$LookupItem { - int get id => throw _privateConstructorUsedError; + int? get id => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError; /// Serializes this LookupItem to a JSON map. @@ -320,7 +390,7 @@ abstract class $LookupItemCopyWith<$Res> { LookupItem value, $Res Function(LookupItem) then) = _$LookupItemCopyWithImpl<$Res, LookupItem>; @useResult - $Res call({int id, String name}); + $Res call({int? id, String name}); } /// @nodoc @@ -338,14 +408,14 @@ class _$LookupItemCopyWithImpl<$Res, $Val extends LookupItem> @pragma('vm:prefer-inline') @override $Res call({ - Object? id = null, + Object? id = freezed, Object? name = null, }) { return _then(_value.copyWith( - id: null == id + id: freezed == id ? _value.id : id // ignore: cast_nullable_to_non_nullable - as int, + as int?, name: null == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -362,7 +432,7 @@ abstract class _$$LookupItemImplCopyWith<$Res> __$$LookupItemImplCopyWithImpl<$Res>; @override @useResult - $Res call({int id, String name}); + $Res call({int? id, String name}); } /// @nodoc @@ -378,14 +448,14 @@ class __$$LookupItemImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? id = null, + Object? id = freezed, Object? name = null, }) { return _then(_$LookupItemImpl( - id: null == id + id: freezed == id ? _value.id : id // ignore: cast_nullable_to_non_nullable - as int, + as int?, name: null == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -397,13 +467,13 @@ class __$$LookupItemImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() class _$LookupItemImpl implements _LookupItem { - const _$LookupItemImpl({required this.id, required this.name}); + const _$LookupItemImpl({this.id, required this.name}); factory _$LookupItemImpl.fromJson(Map json) => _$$LookupItemImplFromJson(json); @override - final int id; + final int? id; @override final String name; @@ -442,14 +512,14 @@ class _$LookupItemImpl implements _LookupItem { } abstract class _LookupItem implements LookupItem { - const factory _LookupItem( - {required final int id, required final String name}) = _$LookupItemImpl; + const factory _LookupItem({final int? id, required final String name}) = + _$LookupItemImpl; factory _LookupItem.fromJson(Map json) = _$LookupItemImpl.fromJson; @override - int get id; + int? get id; @override String get name; @@ -467,7 +537,7 @@ EquipmentNameItem _$EquipmentNameItemFromJson(Map json) { /// @nodoc mixin _$EquipmentNameItem { - int get id => throw _privateConstructorUsedError; + int? get id => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError; @JsonKey(name: 'model_number') String? get modelNumber => throw _privateConstructorUsedError; @@ -489,7 +559,7 @@ abstract class $EquipmentNameItemCopyWith<$Res> { _$EquipmentNameItemCopyWithImpl<$Res, EquipmentNameItem>; @useResult $Res call( - {int id, + {int? id, String name, @JsonKey(name: 'model_number') String? modelNumber}); } @@ -509,15 +579,15 @@ class _$EquipmentNameItemCopyWithImpl<$Res, $Val extends EquipmentNameItem> @pragma('vm:prefer-inline') @override $Res call({ - Object? id = null, + Object? id = freezed, Object? name = null, Object? modelNumber = freezed, }) { return _then(_value.copyWith( - id: null == id + id: freezed == id ? _value.id : id // ignore: cast_nullable_to_non_nullable - as int, + as int?, name: null == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -539,7 +609,7 @@ abstract class _$$EquipmentNameItemImplCopyWith<$Res> @override @useResult $Res call( - {int id, + {int? id, String name, @JsonKey(name: 'model_number') String? modelNumber}); } @@ -557,15 +627,15 @@ class __$$EquipmentNameItemImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? id = null, + Object? id = freezed, Object? name = null, Object? modelNumber = freezed, }) { return _then(_$EquipmentNameItemImpl( - id: null == id + id: freezed == id ? _value.id : id // ignore: cast_nullable_to_non_nullable - as int, + as int?, name: null == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -582,7 +652,7 @@ class __$$EquipmentNameItemImplCopyWithImpl<$Res> @JsonSerializable() class _$EquipmentNameItemImpl implements _EquipmentNameItem { const _$EquipmentNameItemImpl( - {required this.id, + {this.id, required this.name, @JsonKey(name: 'model_number') this.modelNumber}); @@ -590,7 +660,7 @@ class _$EquipmentNameItemImpl implements _EquipmentNameItem { _$$EquipmentNameItemImplFromJson(json); @override - final int id; + final int? id; @override final String name; @override @@ -636,7 +706,7 @@ class _$EquipmentNameItemImpl implements _EquipmentNameItem { abstract class _EquipmentNameItem implements EquipmentNameItem { const factory _EquipmentNameItem( - {required final int id, + {final int? id, required final String name, @JsonKey(name: 'model_number') final String? modelNumber}) = _$EquipmentNameItemImpl; @@ -645,7 +715,7 @@ abstract class _EquipmentNameItem implements EquipmentNameItem { _$EquipmentNameItemImpl.fromJson; @override - int get id; + int? get id; @override String get name; @override @@ -660,6 +730,202 @@ abstract class _EquipmentNameItem implements EquipmentNameItem { throw _privateConstructorUsedError; } +CategoryCombinationItem _$CategoryCombinationItemFromJson( + Map json) { + return _CategoryCombinationItem.fromJson(json); +} + +/// @nodoc +mixin _$CategoryCombinationItem { + String get category1 => throw _privateConstructorUsedError; + String get category2 => throw _privateConstructorUsedError; + String get category3 => throw _privateConstructorUsedError; + + /// Serializes this CategoryCombinationItem to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of CategoryCombinationItem + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CategoryCombinationItemCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CategoryCombinationItemCopyWith<$Res> { + factory $CategoryCombinationItemCopyWith(CategoryCombinationItem value, + $Res Function(CategoryCombinationItem) then) = + _$CategoryCombinationItemCopyWithImpl<$Res, CategoryCombinationItem>; + @useResult + $Res call({String category1, String category2, String category3}); +} + +/// @nodoc +class _$CategoryCombinationItemCopyWithImpl<$Res, + $Val extends CategoryCombinationItem> + implements $CategoryCombinationItemCopyWith<$Res> { + _$CategoryCombinationItemCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CategoryCombinationItem + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? category1 = null, + Object? category2 = null, + Object? category3 = null, + }) { + return _then(_value.copyWith( + category1: null == category1 + ? _value.category1 + : category1 // ignore: cast_nullable_to_non_nullable + as String, + category2: null == category2 + ? _value.category2 + : category2 // ignore: cast_nullable_to_non_nullable + as String, + category3: null == category3 + ? _value.category3 + : category3 // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CategoryCombinationItemImplCopyWith<$Res> + implements $CategoryCombinationItemCopyWith<$Res> { + factory _$$CategoryCombinationItemImplCopyWith( + _$CategoryCombinationItemImpl value, + $Res Function(_$CategoryCombinationItemImpl) then) = + __$$CategoryCombinationItemImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String category1, String category2, String category3}); +} + +/// @nodoc +class __$$CategoryCombinationItemImplCopyWithImpl<$Res> + extends _$CategoryCombinationItemCopyWithImpl<$Res, + _$CategoryCombinationItemImpl> + implements _$$CategoryCombinationItemImplCopyWith<$Res> { + __$$CategoryCombinationItemImplCopyWithImpl( + _$CategoryCombinationItemImpl _value, + $Res Function(_$CategoryCombinationItemImpl) _then) + : super(_value, _then); + + /// Create a copy of CategoryCombinationItem + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? category1 = null, + Object? category2 = null, + Object? category3 = null, + }) { + return _then(_$CategoryCombinationItemImpl( + category1: null == category1 + ? _value.category1 + : category1 // ignore: cast_nullable_to_non_nullable + as String, + category2: null == category2 + ? _value.category2 + : category2 // ignore: cast_nullable_to_non_nullable + as String, + category3: null == category3 + ? _value.category3 + : category3 // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$CategoryCombinationItemImpl implements _CategoryCombinationItem { + const _$CategoryCombinationItemImpl( + {required this.category1, + required this.category2, + required this.category3}); + + factory _$CategoryCombinationItemImpl.fromJson(Map json) => + _$$CategoryCombinationItemImplFromJson(json); + + @override + final String category1; + @override + final String category2; + @override + final String category3; + + @override + String toString() { + return 'CategoryCombinationItem(category1: $category1, category2: $category2, category3: $category3)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CategoryCombinationItemImpl && + (identical(other.category1, category1) || + other.category1 == category1) && + (identical(other.category2, category2) || + other.category2 == category2) && + (identical(other.category3, category3) || + other.category3 == category3)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, category1, category2, category3); + + /// Create a copy of CategoryCombinationItem + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CategoryCombinationItemImplCopyWith<_$CategoryCombinationItemImpl> + get copyWith => __$$CategoryCombinationItemImplCopyWithImpl< + _$CategoryCombinationItemImpl>(this, _$identity); + + @override + Map toJson() { + return _$$CategoryCombinationItemImplToJson( + this, + ); + } +} + +abstract class _CategoryCombinationItem implements CategoryCombinationItem { + const factory _CategoryCombinationItem( + {required final String category1, + required final String category2, + required final String category3}) = _$CategoryCombinationItemImpl; + + factory _CategoryCombinationItem.fromJson(Map json) = + _$CategoryCombinationItemImpl.fromJson; + + @override + String get category1; + @override + String get category2; + @override + String get category3; + + /// Create a copy of CategoryCombinationItem + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CategoryCombinationItemImplCopyWith<_$CategoryCombinationItemImpl> + get copyWith => throw _privateConstructorUsedError; +} + CategoryItem _$CategoryItemFromJson(Map json) { return _CategoryItem.fromJson(json); } diff --git a/lib/data/models/lookups/lookup_data.g.dart b/lib/data/models/lookups/lookup_data.g.dart index ba0c086..8684062 100644 --- a/lib/data/models/lookups/lookup_data.g.dart +++ b/lib/data/models/lookups/lookup_data.g.dart @@ -18,13 +18,22 @@ _$LookupDataImpl _$$LookupDataImplFromJson(Map json) => .toList() ?? [], equipmentCategories: (json['equipment_categories'] as List?) - ?.map((e) => CategoryItem.fromJson(e as Map)) + ?.map((e) => + CategoryCombinationItem.fromJson(e as Map)) .toList() ?? [], equipmentStatuses: (json['equipment_statuses'] as List?) ?.map((e) => StatusItem.fromJson(e as Map)) .toList() ?? [], + companies: (json['companies'] as List?) + ?.map((e) => LookupItem.fromJson(e as Map)) + .toList() ?? + [], + warehouses: (json['warehouses'] as List?) + ?.map((e) => LookupItem.fromJson(e as Map)) + .toList() ?? + [], ); Map _$$LookupDataImplToJson(_$LookupDataImpl instance) => @@ -33,11 +42,13 @@ Map _$$LookupDataImplToJson(_$LookupDataImpl instance) => 'equipment_names': instance.equipmentNames, 'equipment_categories': instance.equipmentCategories, 'equipment_statuses': instance.equipmentStatuses, + 'companies': instance.companies, + 'warehouses': instance.warehouses, }; _$LookupItemImpl _$$LookupItemImplFromJson(Map json) => _$LookupItemImpl( - id: (json['id'] as num).toInt(), + id: (json['id'] as num?)?.toInt(), name: json['name'] as String, ); @@ -50,7 +61,7 @@ Map _$$LookupItemImplToJson(_$LookupItemImpl instance) => _$EquipmentNameItemImpl _$$EquipmentNameItemImplFromJson( Map json) => _$EquipmentNameItemImpl( - id: (json['id'] as num).toInt(), + id: (json['id'] as num?)?.toInt(), name: json['name'] as String, modelNumber: json['model_number'] as String?, ); @@ -63,6 +74,22 @@ Map _$$EquipmentNameItemImplToJson( 'model_number': instance.modelNumber, }; +_$CategoryCombinationItemImpl _$$CategoryCombinationItemImplFromJson( + Map json) => + _$CategoryCombinationItemImpl( + category1: json['category1'] as String, + category2: json['category2'] as String, + category3: json['category3'] as String, + ); + +Map _$$CategoryCombinationItemImplToJson( + _$CategoryCombinationItemImpl instance) => + { + 'category1': instance.category1, + 'category2': instance.category2, + 'category3': instance.category3, + }; + _$CategoryItemImpl _$$CategoryItemImplFromJson(Map json) => _$CategoryItemImpl( id: json['id'] as String, diff --git a/lib/main.dart b/lib/main.dart index 50c1bd4..5b44ce8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -104,7 +104,7 @@ class SuperportApp extends StatelessWidget { // ๊ธฐ์กด ๋ผ์šฐํŒ… ์ฒ˜๋ฆฌ (ํผ ํ™”๋ฉด๋“ค) switch (settings.name) { - // ์žฅ๋น„ ์ž…๊ณ  ๊ด€๋ จ ๋ผ์šฐํŠธ + // ์žฅ๋น„ ์ž…๊ณ  ๊ด€๋ จ ๋ผ์šฐํŠธ (์ƒˆ๋กœ์šด Lookup API ๊ธฐ๋ฐ˜) case Routes.equipmentInAdd: return MaterialPageRoute( builder: (context) => const EquipmentInFormScreen(), diff --git a/lib/models/equipment_unified_model.dart b/lib/models/equipment_unified_model.dart index 790f95a..8094b4d 100644 --- a/lib/models/equipment_unified_model.dart +++ b/lib/models/equipment_unified_model.dart @@ -26,6 +26,15 @@ class Equipment { final DateTime? nextInspectionDate; // ๋‹ค์Œ ์ ๊ฒ€์ผ final String? equipmentStatus; // ์žฅ๋น„ ์ƒํƒœ + // ์ƒˆ๋กœ์šด ๋ฐฑ์—”๋“œ API ํ•„๋“œ๋“ค (์ปจํŠธ๋กค๋Ÿฌ ํ˜ธํ™˜์„ฑ์šฉ) + final String? equipmentNumber; // ์žฅ๋น„ ๋ฒˆํ˜ธ + final String? modelName; // ๋ชจ๋ธ๋ช… (name๊ณผ ๋™์ผํ•˜์ง€๋งŒ ๋ช…ํ™•์„ฑ์„ ์œ„ํ•ด) + final String? category1; // ๋Œ€๋ถ„๋ฅ˜ (category์™€ ๋งคํ•‘) + final String? category2; // ์ค‘๋ถ„๋ฅ˜ (subCategory์™€ ๋งคํ•‘) + final String? category3; // ์†Œ๋ถ„๋ฅ˜ (subSubCategory์™€ ๋งคํ•‘) + final int? companyId; // ๊ตฌ๋งค์ฒ˜ ํšŒ์‚ฌ ID + final DateTime? purchaseDate; // ๊ตฌ๋งค์ผ + Equipment({ this.id, required this.manufacturer, @@ -49,6 +58,14 @@ class Equipment { this.lastInspectionDate, this.nextInspectionDate, this.equipmentStatus, + // ๋ฐฑ์—”๋“œ API ํ˜ธํ™˜์„ฑ ํ•„๋“œ๋“ค + this.equipmentNumber, + this.modelName, + this.category1, + this.category2, + this.category3, + this.companyId, + this.purchaseDate, }); Map toJson() { @@ -75,6 +92,14 @@ class Equipment { 'lastInspectionDate': lastInspectionDate?.toIso8601String(), 'nextInspectionDate': nextInspectionDate?.toIso8601String(), 'equipmentStatus': equipmentStatus, + // ๋ฐฑ์—”๋“œ API ํ˜ธํ™˜์„ฑ ํ•„๋“œ๋“ค + 'equipmentNumber': equipmentNumber, + 'modelName': modelName, + 'category1': category1, + 'category2': category2, + 'category3': category3, + 'companyId': companyId, + 'purchaseDate': purchaseDate?.toIso8601String(), }; } @@ -112,6 +137,16 @@ class Equipment { ? DateTime.parse(json['nextInspectionDate']) : null, equipmentStatus: json['equipmentStatus'], + // ๋ฐฑ์—”๋“œ API ํ˜ธํ™˜์„ฑ ํ•„๋“œ๋“ค + equipmentNumber: json['equipmentNumber'], + modelName: json['modelName'], + category1: json['category1'], + category2: json['category2'], + category3: json['category3'], + companyId: json['companyId'], + purchaseDate: json['purchaseDate'] != null + ? DateTime.parse(json['purchaseDate']) + : null, ); } } diff --git a/lib/screens/equipment/controllers/equipment_in_form_controller.dart b/lib/screens/equipment/controllers/equipment_in_form_controller.dart index ebc11ea..425b4ec 100644 --- a/lib/screens/equipment/controllers/equipment_in_form_controller.dart +++ b/lib/screens/equipment/controllers/equipment_in_form_controller.dart @@ -8,6 +8,7 @@ import 'package:superport/utils/constants.dart'; import 'package:superport/core/errors/failures.dart'; import 'package:superport/core/utils/debug_logger.dart'; import 'package:superport/core/utils/equipment_status_converter.dart'; +import 'package:superport/core/services/lookups_service.dart'; /// ์žฅ๋น„ ์ž…๊ณ  ํผ ์ปจํŠธ๋กค๋Ÿฌ /// @@ -16,6 +17,7 @@ class EquipmentInFormController extends ChangeNotifier { final EquipmentService _equipmentService = GetIt.instance(); final WarehouseService _warehouseService = GetIt.instance(); final CompanyService _companyService = GetIt.instance(); + final LookupsService _lookupsService = GetIt.instance(); final int? equipmentInId; // ์‹ค์ œ๋กœ๋Š” ์žฅ๋น„ ID (์ž…๊ณ  ID๊ฐ€ ์•„๋‹˜) int? actualEquipmentId; // API ํ˜ธ์ถœ์šฉ ์‹ค์ œ ์žฅ๋น„ ID @@ -28,37 +30,114 @@ class EquipmentInFormController extends ChangeNotifier { bool get isLoading => _isLoading; String? get error => _error; bool get isSaving => _isSaving; + + // ์ €์žฅ ๊ฐ€๋Šฅ ์ƒํƒœ๋ฅผ ๋ณ„๋„ ๋ณ€์ˆ˜๋กœ ๊ด€๋ฆฌ (์„ฑ๋Šฅ ๊ฐœ์„  ๋ฐ UI ๋™๊ธฐํ™”) + bool _canSave = false; + bool get canSave => _canSave; + + /// canSave ์ƒํƒœ ์—…๋ฐ์ดํŠธ (UI ๋ Œ๋”๋ง ๋ฌธ์ œ ํ•ด๊ฒฐ) + void _updateCanSave() { + final hasEquipmentNumber = _equipmentNumber.trim().isNotEmpty; + final hasManufacturer = _manufacturer.trim().isNotEmpty; + final isNotSaving = !_isSaving; + + final newCanSave = isNotSaving && hasEquipmentNumber && hasManufacturer; + + if (_canSave != newCanSave) { + _canSave = newCanSave; + print('๐Ÿš€ [canSave ์ƒํƒœ ๋ณ€๊ฒฝ] $_canSave โ†’ equipmentNumber: "$_equipmentNumber", manufacturer: "$_manufacturer"'); + notifyListeners(); // ๋ช…์‹œ์  UI ์—…๋ฐ์ดํŠธ + } + } // ํผ ํ‚ค final GlobalKey formKey = GlobalKey(); - // ์ž…๋ ฅ ์ƒํƒœ ๋ณ€์ˆ˜ - String manufacturer = ''; - String name = ''; - String category = ''; - String subCategory = ''; - String subSubCategory = ''; - String serialNumber = ''; - String barcode = ''; - int quantity = 1; - DateTime inDate = DateTime.now(); - String equipmentType = EquipmentType.new_; - bool hasSerialNumber = true; + // ์ž…๋ ฅ ์ƒํƒœ ๋ณ€์ˆ˜ (๋ฐฑ์—”๋“œ API ๊ตฌ์กฐ์— ๋งž๊ฒŒ ์ˆ˜์ •) + String _equipmentNumber = ''; // ์žฅ๋น„๋ฒˆํ˜ธ (ํ•„์ˆ˜) - private์œผ๋กœ ๋ณ€๊ฒฝ + String _manufacturer = ''; // ์ œ์กฐ์‚ฌ (ํ•„์ˆ˜) - private์œผ๋กœ ๋ณ€๊ฒฝ + String _modelName = ''; // ๋ชจ๋ธ๋ช… - private์œผ๋กœ ๋ณ€๊ฒฝ + String _serialNumber = ''; // ์‹œ๋ฆฌ์–ผ๋ฒˆํ˜ธ - private์œผ๋กœ ๋ณ€๊ฒฝ + String _category1 = ''; // ๋Œ€๋ถ„๋ฅ˜ - private์œผ๋กœ ๋ณ€๊ฒฝ + String _category2 = ''; // ์ค‘๋ถ„๋ฅ˜ - private์œผ๋กœ ๋ณ€๊ฒฝ + String _category3 = ''; // ์†Œ๋ถ„๋ฅ˜ - private์œผ๋กœ ๋ณ€๊ฒฝ - // ์›Œ๋Ÿฐํ‹ฐ ๊ด€๋ จ ์ƒํƒœ - String? warrantyLicense; - String? warrantyCode; // ์›Œ๋Ÿฐํ‹ฐ ์ฝ”๋“œ(ํ…์ŠคํŠธ ์ž…๋ ฅ) - DateTime warrantyStartDate = DateTime.now(); - DateTime warrantyEndDate = DateTime.now().add(const Duration(days: 365)); - List warrantyLicenses = []; + // Getters and Setters for reactive fields + String get equipmentNumber => _equipmentNumber; + set equipmentNumber(String value) { + if (_equipmentNumber != value) { + _equipmentNumber = value; + _updateCanSave(); // canSave ์ƒํƒœ ์—…๋ฐ์ดํŠธ + print('DEBUG [Controller] equipmentNumber updated: "$_equipmentNumber"'); + } + } - // ์ž๋™์™„์„ฑ ๋ฐ์ดํ„ฐ + String get serialNumber => _serialNumber; + set serialNumber(String value) { + if (_serialNumber != value) { + _serialNumber = value; + _updateCanSave(); // canSave ์ƒํƒœ ์—…๋ฐ์ดํŠธ + print('DEBUG [Controller] serialNumber updated: "$_serialNumber"'); + } + } + + String get manufacturer => _manufacturer; + set manufacturer(String value) { + if (_manufacturer != value) { + _manufacturer = value; + _updateCanSave(); // canSave ์ƒํƒœ ์—…๋ฐ์ดํŠธ + print('DEBUG [Controller] manufacturer updated: "$_manufacturer"'); + } + } + + String get modelName => _modelName; + set modelName(String value) { + if (_modelName != value) { + _modelName = value; + _updateCanSave(); // canSave ์ƒํƒœ ์—…๋ฐ์ดํŠธ + print('DEBUG [Controller] modelName updated: "$_modelName"'); + } + } + + String get category1 => _category1; + set category1(String value) { + if (_category1 != value) { + _category1 = value; + _updateCanSave(); // canSave ์ƒํƒœ ์—…๋ฐ์ดํŠธ + } + } + + String get category2 => _category2; + set category2(String value) { + if (_category2 != value) { + _category2 = value; + _updateCanSave(); // canSave ์ƒํƒœ ์—…๋ฐ์ดํŠธ + } + } + + String get category3 => _category3; + set category3(String value) { + if (_category3 != value) { + _category3 = value; + _updateCanSave(); // canSave ์ƒํƒœ ์—…๋ฐ์ดํŠธ + } + } + DateTime? purchaseDate; // ๊ตฌ๋งค์ผ + double? purchasePrice; // ๊ตฌ๋งค๊ฐ€๊ฒฉ + + // ์‚ญ์ œ๋œ ํ•„๋“œ๋“ค (๋ฐฑ์—”๋“œ ๋ฏธ์ง€์›) + // barcode, quantity, inDate, equipmentType, hasSerialNumber + // warranty ๊ด€๋ จ ๋ชจ๋“  ํ•„๋“œ๋“ค + + // Lookups API ๋ฐ์ดํ„ฐ (๋งค๋ฒˆ API ํ˜ธ์ถœ) List manufacturers = []; List equipmentNames = []; - // ์นดํ…Œ๊ณ ๋ฆฌ ์ž๋™์™„์„ฑ ๋ฐ์ดํ„ฐ - List categories = []; - List subCategories = []; - List subSubCategories = []; + Map companies = {}; + Map warehouses = {}; + + // ์„ ํƒ๋œ ID ๊ฐ’๋“ค + int? selectedCompanyId; // ๊ตฌ๋งค์ฒ˜ ID + int? selectedWarehouseId; // ์ž…๊ณ ์ง€ ID // ์ฐฝ๊ณ  ์œ„์น˜ ์ „์ฒด ๋ฐ์ดํ„ฐ (์ด๋ฆ„-ID ๋งคํ•‘์šฉ) Map warehouseLocationMap = {}; @@ -66,33 +145,41 @@ class EquipmentInFormController extends ChangeNotifier { // ํŽธ์ง‘ ๋ชจ๋“œ ์—ฌ๋ถ€ bool isEditMode = false; + // ์ˆ˜์ •๋ถˆ๊ฐ€ ํ•„๋“œ ๋ชฉ๋ก (์ˆ˜์ • ๋ชจ๋“œ์—์„œ๋งŒ ์ ์šฉ) + static const List _readOnlyFields = [ + 'equipmentNumber', // ์žฅ๋น„๋ฒˆํ˜ธ + 'manufacturer', // ์ œ์กฐ์‚ฌ + 'modelName', // ๋ชจ๋ธ๋ช… + 'serialNumber', // ์‹œ๋ฆฌ์–ผ๋ฒˆํ˜ธ + 'purchaseDate', // ๊ตฌ๋งค์ผ + 'purchasePrice', // ๊ตฌ๋งค๊ฐ€๊ฒฉ + ]; + + /// ํŠน์ • ํ•„๋“œ๊ฐ€ ์ˆ˜์ •๋ถˆ๊ฐ€์ธ์ง€ ํ™•์ธ + bool isFieldReadOnly(String fieldName) { + return isEditMode && _readOnlyFields.contains(fieldName); + } + // ์ž…๊ณ ์ง€, ํŒŒํŠธ๋„ˆ์‚ฌ ๊ด€๋ จ ์ƒํƒœ String? warehouseLocation; String? partnerCompany; List warehouseLocations = []; List partnerCompanies = []; - // ์ƒˆ๋กœ์šด ํ•„๋“œ๋“ค (๋ฐฑ์—”๋“œ API ๊ตฌ์กฐ ๋ณ€๊ฒฝ ๋Œ€์‘) - double? purchasePrice; // ๊ตฌ๋งค ๊ฐ€๊ฒฉ - int? currentCompanyId; // ํ˜„์žฌ ํšŒ์‚ฌ ID - int? warehouseLocationId; // ์ฐฝ๊ณ  ์œ„์น˜ ID - int? currentBranchId; // ํ˜„์žฌ ์ง€์  ID (Deprecated) - DateTime? lastInspectionDate; // ์ตœ๊ทผ ์ ๊ฒ€์ผ - DateTime? nextInspectionDate; // ๋‹ค์Œ ์ ๊ฒ€์ผ - String? equipmentStatus; // ์žฅ๋น„ ์ƒํƒœ + // ๊ธฐ์กด ํ•„๋“œ๋“ค์€ ์œ„๋กœ ์ด๋™, ์‚ญ์ œ๋œ ํ•„๋“œ๋“ค + // currentCompanyId, currentBranchId, lastInspectionDate, nextInspectionDate, equipmentStatus๋Š” ์ž…๊ณ  ์‹œ ๋ถˆํ•„์š” + + // ์›Œ๋Ÿฐํ‹ฐ ๊ด€๋ จ ํ•„๋“œ๋“ค (ํ•„์š” ์‹œ ์‚ฌ์šฉ) + String? warrantyLicense; + DateTime warrantyStartDate = DateTime.now(); + DateTime warrantyEndDate = DateTime.now().add(const Duration(days: 365)); final TextEditingController remarkController = TextEditingController(); EquipmentInFormController({this.equipmentInId}) { isEditMode = equipmentInId != null; - _loadManufacturers(); - _loadEquipmentNames(); - _loadCategories(); - _loadSubCategories(); - _loadSubSubCategories(); - _loadWarehouseLocations(); - _loadPartnerCompanies(); - _loadWarrantyLicenses(); + _loadDropdownData(); + _updateCanSave(); // ์ดˆ๊ธฐ canSave ์ƒํƒœ ์„ค์ • // ์ˆ˜์ • ๋ชจ๋“œ์ผ ๋•Œ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ๋Š” initializeForEdit() ๋ฉ”์„œ๋“œ๋กœ ์ด๋™ } @@ -102,79 +189,50 @@ class EquipmentInFormController extends ChangeNotifier { await _loadEquipmentIn(); } - // ์ž๋™์™„์„ฑ ๋ฐ์ดํ„ฐ๋Š” API๋ฅผ ํ†ตํ•ด ๋กœ๋“œํ•ด์•ผ ํ•˜์ง€๋งŒ, ํ˜„์žฌ๋Š” ๋นˆ ๋ชฉ๋ก์œผ๋กœ ์„ค์ • - void _loadManufacturers() { - // TODO: API๋ฅผ ํ†ตํ•ด ์ œ์กฐ์‚ฌ ๋ชฉ๋ก ๋กœ๋“œ - manufacturers = []; - } - - void _loadEquipmentNames() { - // TODO: API๋ฅผ ํ†ตํ•ด ์žฅ๋น„๋ช… ๋ชฉ๋ก ๋กœ๋“œ - equipmentNames = []; - } - - void _loadCategories() { - // TODO: API๋ฅผ ํ†ตํ•ด ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก ๋กœ๋“œ - categories = []; - } - - void _loadSubCategories() { - // TODO: API๋ฅผ ํ†ตํ•ด ์„œ๋ธŒ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก ๋กœ๋“œ - subCategories = []; - } - - void _loadSubSubCategories() { - // TODO: API๋ฅผ ํ†ตํ•ด ์„œ๋ธŒ์„œ๋ธŒ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก ๋กœ๋“œ - subSubCategories = []; - } - - // ์ž…๊ณ ์ง€ ๋ชฉ๋ก ๋กœ๋“œ - void _loadWarehouseLocations() async { + // ๋“œ๋กญ๋‹ค์šด ๋ฐ์ดํ„ฐ ๋กœ๋“œ (๋งค๋ฒˆ API ํ˜ธ์ถœ) + void _loadDropdownData() async { try { - DebugLogger.log('์ž…๊ณ ์ง€ ๋ชฉ๋ก API ๋กœ๋“œ ์‹œ์ž‘', tag: 'EQUIPMENT_IN'); - final response = await _warehouseService.getWarehouseLocations(); - warehouseLocations = response.items.map((e) => e.name).toList(); - // ์ด๋ฆ„-ID ๋งคํ•‘ ์ €์žฅ - warehouseLocationMap = {for (var loc in response.items) loc.name: loc.id}; - DebugLogger.log('์ž…๊ณ ์ง€ ๋ชฉ๋ก ๋กœ๋“œ ์„ฑ๊ณต', tag: 'EQUIPMENT_IN', data: { - 'count': warehouseLocations.length, - 'locations': warehouseLocations, - 'locationMap': warehouseLocationMap, - }); - notifyListeners(); + DebugLogger.log('Equipment ํผ ๋“œ๋กญ๋‹ค์šด ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹œ์ž‘', tag: 'EQUIPMENT_IN'); + final result = await _lookupsService.getEquipmentFormDropdownData(); + + result.fold( + (failure) { + DebugLogger.logError('๋“œ๋กญ๋‹ค์šด ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ', error: failure.message); + // API ์‹คํŒจ ์‹œ ๋นˆ ๋ฐ์ดํ„ฐ + manufacturers = []; + equipmentNames = []; + companies = {}; + warehouses = {}; + notifyListeners(); + }, + (data) { + manufacturers = data['manufacturers'] as List; + equipmentNames = data['equipment_names'] as List; + companies = data['companies'] as Map; + warehouses = data['warehouses'] as Map; + + DebugLogger.log('๋“œ๋กญ๋‹ค์šด ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์„ฑ๊ณต', tag: 'EQUIPMENT_IN', data: { + 'manufacturers_count': manufacturers.length, + 'equipment_names_count': equipmentNames.length, + 'companies_count': companies.length, + 'warehouses_count': warehouses.length, + }); + + notifyListeners(); + }, + ); } catch (e) { - DebugLogger.logError('์ž…๊ณ ์ง€ ๋ชฉ๋ก ๋กœ๋“œ ์‹คํŒจ', error: e); - // API ์‹คํŒจ ์‹œ ๋นˆ ๋ชฉ๋ก - warehouseLocations = []; - warehouseLocationMap = {}; + DebugLogger.logError('๋“œ๋กญ๋‹ค์šด ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์˜ˆ์™ธ', error: e); + manufacturers = []; + equipmentNames = []; + companies = {}; + warehouses = {}; notifyListeners(); } } - // ํŒŒํŠธ๋„ˆ์‚ฌ ๋ชฉ๋ก ๋กœ๋“œ - void _loadPartnerCompanies() async { - try { - DebugLogger.log('ํŒŒํŠธ๋„ˆ์‚ฌ ๋ชฉ๋ก API ๋กœ๋“œ ์‹œ์ž‘', tag: 'EQUIPMENT_IN'); - final response = await _companyService.getCompanies(); - partnerCompanies = response.items.map((c) => c.name).toList(); - DebugLogger.log('ํŒŒํŠธ๋„ˆ์‚ฌ ๋ชฉ๋ก ๋กœ๋“œ ์„ฑ๊ณต', tag: 'EQUIPMENT_IN', data: { - 'count': partnerCompanies.length, - 'companies': partnerCompanies, - }); - notifyListeners(); - } catch (e) { - DebugLogger.logError('ํŒŒํŠธ๋„ˆ์‚ฌ ๋ชฉ๋ก ๋กœ๋“œ ์‹คํŒจ', error: e); - // API ์‹คํŒจ ์‹œ ๋นˆ ๋ชฉ๋ก - partnerCompanies = []; - notifyListeners(); - } - } - - // ์›Œ๋Ÿฐํ‹ฐ ๋ผ์ด์„ผ์Šค ๋ชฉ๋ก ๋กœ๋“œ - void _loadWarrantyLicenses() { - // ์‹ค์ œ๋กœ๋Š” API๋‚˜ ์„œ๋น„์Šค์—์„œ ๋ถˆ๋Ÿฌ์™€์•ผ ํ•˜์ง€๋งŒ, ํŒŒํŠธ๋„ˆ์‚ฌ์™€ ๋™์ผํ•œ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ - warrantyLicenses = List.from(partnerCompanies); - } + // ๊ธฐ์กด์˜ ๊ฐœ๋ณ„ ๋กœ๋“œ ๋ฉ”์„œ๋“œ๋“ค์€ _loadDropdownData()๋กœ ํ†ตํ•ฉ๋จ + // warehouseLocations, partnerCompanies ๋ฆฌ์ŠคํŠธ ๋ณ€์ˆ˜๋“ค๋„ ์ œ๊ฑฐ๋จ // ๊ธฐ์กด ๋ฐ์ดํ„ฐ ๋กœ๋“œ(์ˆ˜์ • ๋ชจ๋“œ) Future _loadEquipmentIn() async { @@ -205,60 +263,43 @@ class EquipmentInFormController extends ChangeNotifier { 'name': equipment.name, }); - // ์žฅ๋น„ ์ •๋ณด ์„ค์ • + // ์žฅ๋น„ ์ •๋ณด ์„ค์ • (์ƒˆ๋กœ์šด ํ•„๋“œ ๊ตฌ์กฐ) print('DEBUG [_loadEquipmentIn] Setting equipment data...'); print('DEBUG [_loadEquipmentIn] equipment.manufacturer="${equipment.manufacturer}"'); - print('DEBUG [_loadEquipmentIn] equipment.name="${equipment.name}"'); + print('DEBUG [_loadEquipmentIn] equipment.equipmentNumber="${equipment.equipmentNumber}"'); - manufacturer = equipment.manufacturer; - name = equipment.name; - category = equipment.category; - subCategory = equipment.subCategory; - subSubCategory = equipment.subSubCategory; - serialNumber = equipment.serialNumber ?? ''; - barcode = equipment.barcode ?? ''; - quantity = equipment.quantity; + // ์ƒˆ๋กœ์šด ํ•„๋“œ ๊ตฌ์กฐ๋กœ ๋งคํ•‘ (setter ์‚ฌ์šฉํ•˜์—ฌ notifyListeners ์ž๋™ ํ˜ธ์ถœ) + _equipmentNumber = equipment.equipmentNumber ?? ''; + manufacturer = equipment.manufacturer ?? ''; + modelName = equipment.modelName ?? equipment.name ?? ''; + _serialNumber = equipment.serialNumber ?? ''; + category1 = equipment.category1 ?? equipment.category ?? ''; + category2 = equipment.category2 ?? equipment.subCategory ?? ''; + category3 = equipment.category3 ?? equipment.subSubCategory ?? ''; + purchaseDate = equipment.purchaseDate; + purchasePrice = equipment.purchasePrice; + selectedCompanyId = equipment.companyId; + selectedWarehouseId = equipment.warehouseLocationId; remarkController.text = equipment.remark ?? ''; - hasSerialNumber = serialNumber.isNotEmpty; - print('DEBUG [_loadEquipmentIn] After setting - manufacturer="$manufacturer", name="$name"'); + print('DEBUG [_loadEquipmentIn] After setting - equipmentNumber="$_equipmentNumber", manufacturer="$_manufacturer", modelName="$_modelName"'); + // ๐Ÿ”ง [DEBUG] UI ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•œ ์ค‘์š” ํ•„๋“œ๋“ค ๋กœ๊น… + print('DEBUG [_loadEquipmentIn] purchaseDate: $purchaseDate, purchasePrice: $purchasePrice'); + print('DEBUG [_loadEquipmentIn] selectedCompanyId: $selectedCompanyId, selectedWarehouseId: $selectedWarehouseId'); DebugLogger.log('์žฅ๋น„ ๋ฐ์ดํ„ฐ ์„ค์ • ์™„๋ฃŒ', tag: 'EQUIPMENT_IN', data: { - 'manufacturer': manufacturer, - 'name': name, - 'category': category, - 'subCategory': subCategory, - 'subSubCategory': subSubCategory, - 'serialNumber': serialNumber, - 'quantity': quantity, + 'equipmentNumber': _equipmentNumber, + 'manufacturer': _manufacturer, + 'modelName': _modelName, + 'category1': _category1, + 'category2': _category2, + 'category3': _category3, + 'serialNumber': _serialNumber, }); - print('DEBUG [EQUIPMENT_IN]: Equipment loaded - manufacturer: "$manufacturer", name: "$name", category: "$category"'); + print('DEBUG [EQUIPMENT_IN]: Equipment loaded - equipmentNumber: "$_equipmentNumber", manufacturer: "$_manufacturer", modelName: "$_modelName"'); - // ์›Œ๋Ÿฐํ‹ฐ ์ •๋ณด - warrantyLicense = equipment.warrantyLicense; - warrantyStartDate = equipment.warrantyStartDate ?? DateTime.now(); - warrantyEndDate = equipment.warrantyEndDate ?? DateTime.now().add(const Duration(days: 365)); - - // ์ƒˆ๋กœ์šด ํ•„๋“œ๋“ค ์„ค์ • (๋ฐฑ์—”๋“œ API์—์„œ ์ œ๊ณต๋˜๋ฉด ์‚ฌ์šฉ, ์•„๋‹ˆ๋ฉด ๊ธฐ๋ณธ๊ฐ’) - currentCompanyId = equipment.currentCompanyId; - currentBranchId = equipment.currentBranchId; - lastInspectionDate = equipment.lastInspectionDate; - nextInspectionDate = equipment.nextInspectionDate; - // ์œ ํšจํ•œ ์žฅ๋น„ ์ƒํƒœ ๋ชฉ๋ก (ํด๋ผ์ด์–ธํŠธ ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜) - const validServerStatuses = ['available', 'inuse', 'maintenance', 'disposed']; - if (equipment.equipmentStatus != null && validServerStatuses.contains(equipment.equipmentStatus)) { - // ์„œ๋ฒ„ ์ƒํƒœ๋ฅผ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ €์žฅ - equipmentStatus = EquipmentStatusConverter.serverToClient(equipment.equipmentStatus); - } else { - // ๊ธฐ๋ณธ๊ฐ’: ์ž…๊ณ  ์ƒํƒœ (ํด๋ผ์ด์–ธํŠธ ํ˜•์‹) - equipmentStatus = 'I'; // ์ž…๊ณ  - } - - // ์ž…๊ณ  ๊ด€๋ จ ์ •๋ณด๋Š” ํ˜„์žฌ API์—์„œ ์ œ๊ณตํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ - inDate = equipment.inDate ?? DateTime.now(); - equipmentType = EquipmentType.new_; - // ์ฐฝ๊ณ  ์œ„์น˜์™€ ํŒŒํŠธ๋„ˆ์‚ฌ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ˆ˜์ • ์‹œ ์ž…๋ ฅ + // ์ถ”๊ฐ€ ํ•„๋“œ๋“ค์€ ์ž…๊ณ  ์‹œ์—๋Š” ํ•„์š”ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ƒ๋žต } catch (e, stackTrace) { print('DEBUG [_loadEquipmentIn] Error loading equipment: $e'); @@ -271,6 +312,7 @@ class EquipmentInFormController extends ChangeNotifier { DebugLogger.logError('์žฅ๋น„ ๋กœ๋“œ ์‹คํŒจ', error: e); } finally { _isLoading = false; + _updateCanSave(); // ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์™„๋ฃŒ ์‹œ canSave ์ƒํƒœ ์—…๋ฐ์ดํŠธ notifyListeners(); } } @@ -308,66 +350,43 @@ class EquipmentInFormController extends ChangeNotifier { _isSaving = true; _error = null; + _updateCanSave(); // ์ €์žฅ ์‹œ์ž‘ ์‹œ canSave ์ƒํƒœ ์—…๋ฐ์ดํŠธ notifyListeners(); try { - // ์ž…๋ ฅ๊ฐ’์ด ๋ฆฌ์ŠคํŠธ์— ์—†์œผ๋ฉด ์ถ”๊ฐ€ - if (partnerCompany != null && - partnerCompany!.isNotEmpty && - !partnerCompanies.contains(partnerCompany)) { - partnerCompanies.add(partnerCompany!); - } - if (warehouseLocation != null && - warehouseLocation!.isNotEmpty && - !warehouseLocations.contains(warehouseLocation)) { - warehouseLocations.add(warehouseLocation!); - } - if (manufacturer.isNotEmpty && !manufacturers.contains(manufacturer)) { - manufacturers.add(manufacturer); - } - if (name.isNotEmpty && !equipmentNames.contains(name)) { - equipmentNames.add(name); - } - if (category.isNotEmpty && !categories.contains(category)) { - categories.add(category); - } - if (subCategory.isNotEmpty && !subCategories.contains(subCategory)) { - subCategories.add(subCategory); - } - if (subSubCategory.isNotEmpty && - !subSubCategories.contains(subSubCategory)) { - subSubCategories.add(subSubCategory); - } - if (warrantyLicense != null && - warrantyLicense!.isNotEmpty && - !warrantyLicenses.contains(warrantyLicense)) { - warrantyLicenses.add(warrantyLicense!); - } + // ์ƒˆ๋กœ์šด ์ž…๋ ฅ๊ฐ’์„ ๋กœ์ปฌ ๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€ (์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ํ–ฅ์ƒ์šฉ) + if (_manufacturer.isNotEmpty && !manufacturers.contains(_manufacturer)) { + manufacturers.add(_manufacturer); + } + if (_modelName.isNotEmpty && !equipmentNames.contains(_modelName)) { + equipmentNames.add(_modelName); + } + // ๋ฐฑ์—”๋“œ API ๊ตฌ์กฐ์— ๋งž๋Š” Equipment ๊ฐ์ฒด ์ƒ์„ฑ final equipment = Equipment( - manufacturer: manufacturer, - name: name, - category: category, - subCategory: subCategory, - subSubCategory: subSubCategory, - serialNumber: hasSerialNumber ? serialNumber : null, - barcode: barcode.isNotEmpty ? barcode : null, - quantity: quantity, - inDate: inDate, // ๊ตฌ๋งค์ผ ๋งคํ•‘ - remark: remarkController.text.trim().isEmpty ? null : remarkController.text.trim(), - warrantyLicense: warrantyLicense, - // ๋ฐฑ์—”๋“œ API ์ƒˆ๋กœ์šด ํ•„๋“œ๋“ค ๋งคํ•‘ + // ์ƒˆ๋กœ์šด ํ•„๋“œ๋“ค (๋ฐฑ์—”๋“œ API ๊ธฐ์ค€) + equipmentNumber: _equipmentNumber.trim(), + manufacturer: _manufacturer.trim(), + modelName: _modelName.trim().isEmpty ? null : _modelName.trim(), + serialNumber: _serialNumber.trim().isEmpty ? null : _serialNumber.trim(), + category1: _category1.trim().isEmpty ? null : _category1.trim(), + category2: _category2.trim().isEmpty ? null : _category2.trim(), + category3: _category3.trim().isEmpty ? null : _category3.trim(), + purchaseDate: purchaseDate, purchasePrice: purchasePrice, - currentCompanyId: currentCompanyId, - warehouseLocationId: warehouseLocationId, - currentBranchId: currentBranchId, // Deprecated but kept for compatibility - lastInspectionDate: lastInspectionDate, - nextInspectionDate: nextInspectionDate, - equipmentStatus: equipmentStatus, // ํด๋ผ์ด์–ธํŠธ ํ˜•์‹ ('I', 'O' ๋“ฑ) - warrantyStartDate: warrantyStartDate, - warrantyEndDate: warrantyEndDate, - // ์›Œ๋Ÿฐํ‹ฐ ์ฝ”๋“œ ์ €์žฅ ํ•„์š”์‹œ ์—ฌ๊ธฐ์— ์ถ”๊ฐ€ + remark: remarkController.text.trim().isEmpty ? null : remarkController.text.trim(), + companyId: selectedCompanyId, // ๊ตฌ๋งค์ฒ˜ ID + warehouseLocationId: selectedWarehouseId, // ์ž…๊ณ ์ง€ ID + + // ๊ธฐ์กด Equipment ๋ชจ๋ธ๊ณผ์˜ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•œ ๋งคํ•‘ + name: _modelName.trim().isEmpty ? _equipmentNumber.trim() : _modelName.trim(), + category: _category1.trim(), + subCategory: _category2.trim(), + subSubCategory: _category3.trim(), + barcode: '', // ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ + quantity: 1, // ๊ธฐ๋ณธ๊ฐ’ + inDate: purchaseDate ?? DateTime.now(), ); // API ํ˜ธ์ถœ @@ -379,8 +398,9 @@ class EquipmentInFormController extends ChangeNotifier { DebugLogger.log('์žฅ๋น„ ์ •๋ณด ์—…๋ฐ์ดํŠธ ์‹œ์ž‘', tag: 'EQUIPMENT_IN', data: { 'equipmentId': actualEquipmentId, + 'equipmentNumber': equipment.equipmentNumber, 'manufacturer': equipment.manufacturer, - 'name': equipment.name, + 'modelName': equipment.modelName, 'serialNumber': equipment.serialNumber, }); @@ -391,10 +411,11 @@ class EquipmentInFormController extends ChangeNotifier { // ์ƒ์„ฑ ๋ชจ๋“œ try { // 1. ๋จผ์ € ์žฅ๋น„ ์ƒ์„ฑ - DebugLogger.log('์žฅ๋น„ ์ƒ์„ฑ ์‹œ์ž‘', tag: 'EQUIPMENT_IN', data: { - 'manufacturer': manufacturer, - 'name': name, - 'serialNumber': serialNumber, + DebugLogger.log('์žฅ๋น„ ์ž…๊ณ  ์‹œ์ž‘', tag: 'EQUIPMENT_IN', data: { + 'equipmentNumber': _equipmentNumber, + 'manufacturer': _manufacturer, + 'modelName': _modelName, + 'serialNumber': _serialNumber, }); final createdEquipment = await _equipmentService.createEquipment(equipment); @@ -403,29 +424,7 @@ class EquipmentInFormController extends ChangeNotifier { 'equipmentId': createdEquipment.id, }); - // 2. ์ž…๊ณ  ์ฒ˜๋ฆฌ (warehouse location ID ํ•„์š”) - int? warehouseLocationId; - if (warehouseLocation != null) { - // ์ €์žฅ๋œ ๋งคํ•‘์—์„œ ID ๊ฐ€์ ธ์˜ค๊ธฐ - warehouseLocationId = warehouseLocationMap[warehouseLocation]; - - if (warehouseLocationId == null) { - DebugLogger.logError('์ฐฝ๊ณ  ์œ„์น˜ ID๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ', error: 'Warehouse: $warehouseLocation'); - } - } - - DebugLogger.log('์ž…๊ณ  ์ฒ˜๋ฆฌ ์‹œ์ž‘', tag: 'EQUIPMENT_IN', data: { - 'equipmentId': createdEquipment.id, - 'quantity': quantity, - 'warehouseLocationId': warehouseLocationId, - }); - - await _equipmentService.equipmentIn( - equipmentId: createdEquipment.id!, - quantity: quantity, - warehouseLocationId: warehouseLocationId, - notes: remarkController.text.trim(), - ); + // ์ƒˆ๋กœ์šด API์—์„œ๋Š” ์žฅ๋น„ ์ƒ์„ฑ ์‹œ ์ž…๊ณ  ์ฒ˜๋ฆฌ๊นŒ์ง€ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌ๋จ DebugLogger.log('์ž…๊ณ  ์ฒ˜๋ฆฌ ์„ฑ๊ณต', tag: 'EQUIPMENT_IN'); @@ -435,15 +434,8 @@ class EquipmentInFormController extends ChangeNotifier { } } - // ์ €์žฅ ํ›„ ๋ฆฌ์ŠคํŠธ ์žฌ๋กœ๋”ฉ (์ค‘๋ณต ๋ฐฉ์ง€ ๋ฐ ์ตœ์‹ ํ™”) - _loadManufacturers(); - _loadEquipmentNames(); - _loadCategories(); - _loadSubCategories(); - _loadSubSubCategories(); - _loadWarehouseLocations(); - _loadPartnerCompanies(); - _loadWarrantyLicenses(); + // ์ €์žฅ ํ›„ ๋“œ๋กญ๋‹ค์šด ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ (๋ฐฑ์—”๋“œ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•ด) + _loadDropdownData(); return true; } on Failure catch (e) { @@ -456,6 +448,7 @@ class EquipmentInFormController extends ChangeNotifier { return false; } finally { _isSaving = false; + _updateCanSave(); // ์ €์žฅ ์™„๋ฃŒ ์‹œ canSave ์ƒํƒœ ์—…๋ฐ์ดํŠธ notifyListeners(); } } diff --git a/lib/screens/equipment/controllers/equipment_list_controller.dart b/lib/screens/equipment/controllers/equipment_list_controller.dart index 91549ba..7f2b854 100644 --- a/lib/screens/equipment/controllers/equipment_list_controller.dart +++ b/lib/screens/equipment/controllers/equipment_list_controller.dart @@ -60,6 +60,7 @@ class EquipmentListController extends BaseListController { } else { throw Exception('LookupsService not registered in GetIt'); } + } @override @@ -67,11 +68,11 @@ class EquipmentListController extends BaseListController { required PaginationParams params, Map? additionalFilters, }) async { - // API ํ˜ธ์ถœ + // API ํ˜ธ์ถœ (ํŽ˜์ด์ง€ ํฌ๊ธฐ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ 10๊ฐœ๋กœ ๊ณ ์ •) final apiEquipmentDtos = await ErrorHandler.handleApiCall( () => _equipmentService.getEquipmentsWithStatus( page: params.page, - perPage: params.perPage, + perPage: 10, // ๐ŸŽฏ ์žฅ๋น„ ๋ฆฌ์ŠคํŠธ๋Š” ํ•ญ์ƒ 10๊ฐœ๋กœ ๊ณ ์ • status: _statusFilter != null ? EquipmentStatusConverter.clientToServer(_statusFilter) : null, search: params.search, @@ -98,7 +99,10 @@ class EquipmentListController extends BaseListController { } // DTO๋ฅผ UnifiedEquipment๋กœ ๋ณ€ํ™˜ + print('DEBUG [EquipmentListController] Converting ${apiEquipmentDtos.items.length} DTOs to UnifiedEquipment'); final items = apiEquipmentDtos.items.map((dto) { + // ๐Ÿ”ง [DEBUG] JOIN๋œ ๋ฐ์ดํ„ฐ ๋กœ๊น… + print('DEBUG [EquipmentListController] DTO ID: ${dto.id}, companyName: "${dto.companyName}", warehouseName: "${dto.warehouseName}"'); final equipment = Equipment( id: dto.id, manufacturer: dto.manufacturer ?? 'Unknown', @@ -113,13 +117,24 @@ class EquipmentListController extends BaseListController { // ๊ฐ„๋‹จํ•œ Company ์ •๋ณด ์ƒ์„ฑ (์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ œ๊ฑฐ) // final company = dto.companyName != null ? ... : null; - return UnifiedEquipment( + final unifiedEquipment = UnifiedEquipment( id: dto.id, equipment: equipment, date: dto.createdAt ?? DateTime.now(), status: EquipmentStatusConverter.serverToClient(dto.status), notes: null, // EquipmentListDto์— remark ํ•„๋“œ ์—†์Œ + // ๐Ÿ”ง [BUG FIX] ๋ˆ„๋ฝ๋œ ์œ„์น˜ ์ •๋ณด ํ•„๋“œ๋“ค ์ถ”๊ฐ€ + // ๋ฌธ์ œ: ์žฅ๋น„ ๋ฆฌ์ŠคํŠธ์—์„œ ์œ„์น˜ ์ •๋ณด(ํ˜„์žฌ ์œ„์น˜, ์ฐฝ๊ณ  ์œ„์น˜)๊ฐ€ ํ‘œ์‹œ๋˜์ง€ ์•Š์Œ + // ์›์ธ: UnifiedEquipment ์ƒ์„ฑ ์‹œ JOIN๋œ ๋ฐ์ดํ„ฐ(companyName, warehouseName) ๋ˆ„๋ฝ + // ํ•ด๊ฒฐ: EquipmentListDto์˜ JOIN๋œ ๋ฐ์ดํ„ฐ๋ฅผ UnifiedEquipment ํ•„๋“œ๋กœ ๋งคํ•‘ + currentCompany: dto.companyName, // API company_name โ†’ currentCompany + warehouseLocation: dto.warehouseName, // API warehouse_name โ†’ warehouseLocation + // currentBranch๋Š” EquipmentListDto์— ์—†์œผ๋ฏ€๋กœ null (๋ฐฑ์—”๋“œ API ๊ตฌ์กฐ ๋ณ€๊ฒฝ์œผ๋กœ ์ง€์  ๊ฐœ๋… ์ œ๊ฑฐ) + currentBranch: null, ); + // ๐Ÿ”ง [DEBUG] ๋ณ€ํ™˜๋œ UnifiedEquipment ๋กœ๊น… (ํ•„์š” ์‹œ ํ™œ์„ฑํ™”) + // print('DEBUG [EquipmentListController] UnifiedEquipment ID: ${unifiedEquipment.id}, currentCompany: "${unifiedEquipment.currentCompany}", warehouseLocation: "${unifiedEquipment.warehouseLocation}"'); + return unifiedEquipment; }).toList(); // API์—์„œ ๋ฐ˜ํ™˜ํ•œ ์‹ค์ œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์‚ฌ์šฉ @@ -400,8 +415,8 @@ class EquipmentListController extends BaseListController { ); } - /// ์บ์‹œ๋œ ์žฅ๋น„ ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก ์กฐํšŒ - List getCachedEquipmentCategories() { + /// ์บ์‹œ๋œ ์žฅ๋น„ ์นดํ…Œ๊ณ ๋ฆฌ ์กฐํ•ฉ ๋ชฉ๋ก ์กฐํšŒ + List getCachedEquipmentCategories() { final result = _lookupsService.getEquipmentCategories(); return result.fold( (failure) => [], diff --git a/lib/screens/equipment/equipment_in_form.dart b/lib/screens/equipment/equipment_in_form.dart index 1f9d0f7..85a651c 100644 --- a/lib/screens/equipment/equipment_in_form.dart +++ b/lib/screens/equipment/equipment_in_form.dart @@ -1,18 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; -// import 'package:superport/models/equipment_unified_model.dart'; -// import 'package:superport/screens/common/custom_widgets.dart' hide FormFieldWrapper; import 'package:superport/screens/common/theme_shadcn.dart'; import 'package:superport/screens/common/templates/form_layout_template.dart'; import 'package:superport/utils/constants.dart'; -// import 'package:flutter_localizations/flutter_localizations.dart'; -// import 'package:superport/screens/equipment/widgets/autocomplete_text_field.dart'; -import 'controllers/equipment_in_form_controller.dart'; -// import 'package:superport/screens/common/widgets/category_autocomplete_field.dart'; -// import 'package:superport/screens/common/widgets/autocomplete_dropdown_field.dart'; +import 'package:superport/utils/currency_formatter.dart'; import 'package:superport/screens/common/widgets/remark_input.dart'; +import 'package:superport/core/widgets/category_cascade_form_field.dart'; +import 'controllers/equipment_in_form_controller.dart'; +/// ์ƒˆ๋กœ์šด Equipment ์ž…๊ณ  ํผ (Lookup API ๊ธฐ๋ฐ˜) class EquipmentInFormScreen extends StatefulWidget { final int? equipmentInId; @@ -24,2561 +21,511 @@ class EquipmentInFormScreen extends StatefulWidget { class _EquipmentInFormScreenState extends State { late EquipmentInFormController _controller; - late FocusNode _manufacturerFocusNode; - late FocusNode _nameFieldFocusNode; - - // ๊ตฌ๋งค์ฒ˜ ๋“œ๋กญ๋‹ค์šด ์˜ค๋ฒ„๋ ˆ์ด ๊ด€๋ จ - final LayerLink _partnerLayerLink = LayerLink(); - OverlayEntry? _partnerOverlayEntry; - final FocusNode _partnerFocusNode = FocusNode(); - late TextEditingController _partnerController; - - // ์ž…๊ณ ์ง€ ๋“œ๋กญ๋‹ค์šด ์˜ค๋ฒ„๋ ˆ์ด ๊ด€๋ จ - final LayerLink _warehouseLayerLink = LayerLink(); - OverlayEntry? _warehouseOverlayEntry; - final FocusNode _warehouseFocusNode = FocusNode(); - late TextEditingController _warehouseController; - - // ์ œ์กฐ์‚ฌ ๋“œ๋กญ๋‹ค์šด ์˜ค๋ฒ„๋ ˆ์ด ๊ด€๋ จ - final LayerLink _manufacturerLayerLink = LayerLink(); - OverlayEntry? _manufacturerOverlayEntry; - late TextEditingController _manufacturerController; - - // ์žฅ๋น„๋ช… ๋“œ๋กญ๋‹ค์šด ์˜ค๋ฒ„๋ ˆ์ด ๊ด€๋ จ - final LayerLink _equipmentNameLayerLink = LayerLink(); - OverlayEntry? _equipmentNameOverlayEntry; - late TextEditingController _equipmentNameController; - - // ๋Œ€๋ถ„๋ฅ˜ ๋“œ๋กญ๋‹ค์šด ์˜ค๋ฒ„๋ ˆ์ด ๊ด€๋ จ - final LayerLink _categoryLayerLink = LayerLink(); - OverlayEntry? _categoryOverlayEntry; - final FocusNode _categoryFocusNode = FocusNode(); - late TextEditingController _categoryController; - - // ์ค‘๋ถ„๋ฅ˜ ๋“œ๋กญ๋‹ค์šด ์˜ค๋ฒ„๋ ˆ์ด ๊ด€๋ จ - final LayerLink _subCategoryLayerLink = LayerLink(); - OverlayEntry? _subCategoryOverlayEntry; - final FocusNode _subCategoryFocusNode = FocusNode(); - late TextEditingController _subCategoryController; - - // ์†Œ๋ถ„๋ฅ˜ ๋“œ๋กญ๋‹ค์šด ์˜ค๋ฒ„๋ ˆ์ด ๊ด€๋ จ - final LayerLink _subSubCategoryLayerLink = LayerLink(); - OverlayEntry? _subSubCategoryOverlayEntry; - final FocusNode _subSubCategoryFocusNode = FocusNode(); - late TextEditingController _subSubCategoryController; - - // ์ถ”๊ฐ€ ํ•„๋“œ ์ปจํŠธ๋กค๋Ÿฌ๋“ค - late TextEditingController _nameController; - late TextEditingController _serialNumberController; - late TextEditingController _barcodeController; - late TextEditingController _quantityController; - late TextEditingController _warrantyCodeController; - - // ํ”„๋กœ๊ทธ๋žจ์  ์ž…๋ ฅ๋ž€ ๋ณ€๊ฒฝ ์—ฌ๋ถ€ ํ”Œ๋ž˜๊ทธ - bool _isProgrammaticPartnerChange = false; - bool _isProgrammaticWarehouseChange = false; - bool _isProgrammaticManufacturerChange = false; - bool _isProgrammaticEquipmentNameChange = false; - bool _isProgrammaticCategoryChange = false; - bool _isProgrammaticSubCategoryChange = false; - bool _isProgrammaticSubSubCategoryChange = false; - - // ์ž…๋ ฅ๋ž€์˜ ์ •ํ™•ํ•œ ์œ„์น˜๋ฅผ ์œ„ํ•œ GlobalKey - final GlobalKey _partnerFieldKey = GlobalKey(); - final GlobalKey _warehouseFieldKey = GlobalKey(); - final GlobalKey _manufacturerFieldKey = GlobalKey(); - final GlobalKey _equipmentNameFieldKey = GlobalKey(); - final GlobalKey _categoryFieldKey = GlobalKey(); - final GlobalKey _subCategoryFieldKey = GlobalKey(); - final GlobalKey _subSubCategoryFieldKey = GlobalKey(); - - // ์ž๋™์™„์„ฑ ํ›„๋ณด(์ž…๋ ฅ๊ฐ’๊ณผ ๊ฐ€์žฅ ๊ทผ์ ‘ํ•œ ํŒŒํŠธ๋„ˆ์‚ฌ) ๊ณ„์‚ฐ ํ•จ์ˆ˜ - String? _getAutocompleteSuggestion(String input) { - if (input.isEmpty) return null; - // ์ž…๋ ฅ๊ฐ’์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ›„๋ณด ์ค‘ ๊ฐ€์žฅ ์งง์€ ๊ฒƒ - final lower = input.toLowerCase(); - final match = _controller.partnerCompanies.firstWhere( - (c) => c.toLowerCase().startsWith(lower), - orElse: () => '', - ); - return match.isNotEmpty && match.length > input.length ? match : null; - } - - // ์ž๋™์™„์„ฑ ํ›„๋ณด(์ž…๋ ฅ๊ฐ’๊ณผ ๊ฐ€์žฅ ๊ทผ์ ‘ํ•œ ์ž…๊ณ ์ง€) ๊ณ„์‚ฐ ํ•จ์ˆ˜ - String? _getWarehouseAutocompleteSuggestion(String input) { - if (input.isEmpty) return null; - // ์ž…๋ ฅ๊ฐ’์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ›„๋ณด ์ค‘ ๊ฐ€์žฅ ์งง์€ ๊ฒƒ - final lower = input.toLowerCase(); - final match = _controller.warehouseLocations.firstWhere( - (c) => c.toLowerCase().startsWith(lower), - orElse: () => '', - ); - return match.isNotEmpty && match.length > input.length ? match : null; - } - - // ์ž๋™์™„์„ฑ ํ›„๋ณด(์ž…๋ ฅ๊ฐ’๊ณผ ๊ฐ€์žฅ ๊ทผ์ ‘ํ•œ ์ œ์กฐ์‚ฌ) ๊ณ„์‚ฐ ํ•จ์ˆ˜ - String? _getManufacturerAutocompleteSuggestion(String input) { - if (input.isEmpty) return null; - // ์ž…๋ ฅ๊ฐ’์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ›„๋ณด ์ค‘ ๊ฐ€์žฅ ์งง์€ ๊ฒƒ - final lower = input.toLowerCase(); - final match = _controller.manufacturers.firstWhere( - (c) => c.toLowerCase().startsWith(lower), - orElse: () => '', - ); - return match.isNotEmpty && match.length > input.length ? match : null; - } - - // ์ž๋™์™„์„ฑ ํ›„๋ณด(์ž…๋ ฅ๊ฐ’๊ณผ ๊ฐ€์žฅ ๊ทผ์ ‘ํ•œ ์žฅ๋น„๋ช…) ๊ณ„์‚ฐ ํ•จ์ˆ˜ - String? _getEquipmentNameAutocompleteSuggestion(String input) { - if (input.isEmpty) return null; - // ์ž…๋ ฅ๊ฐ’์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ›„๋ณด ์ค‘ ๊ฐ€์žฅ ์งง์€ ๊ฒƒ - final lower = input.toLowerCase(); - final match = _controller.equipmentNames.firstWhere( - (c) => c.toLowerCase().startsWith(lower), - orElse: () => '', - ); - return match.isNotEmpty && match.length > input.length ? match : null; - } - - // ์ž๋™์™„์„ฑ ํ›„๋ณด(์ž…๋ ฅ๊ฐ’๊ณผ ๊ฐ€์žฅ ๊ทผ์ ‘ํ•œ ๋Œ€๋ถ„๋ฅ˜) ๊ณ„์‚ฐ ํ•จ์ˆ˜ - String? _getCategoryAutocompleteSuggestion(String input) { - if (input.isEmpty) return null; - // ์ž…๋ ฅ๊ฐ’์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ›„๋ณด ์ค‘ ๊ฐ€์žฅ ์งง์€ ๊ฒƒ - final lower = input.toLowerCase(); - final match = _controller.categories.firstWhere( - (c) => c.toLowerCase().startsWith(lower), - orElse: () => '', - ); - return match.isNotEmpty && match.length > input.length ? match : null; - } - - // ์ž๋™์™„์„ฑ ํ›„๋ณด(์ž…๋ ฅ๊ฐ’๊ณผ ๊ฐ€์žฅ ๊ทผ์ ‘ํ•œ ์ค‘๋ถ„๋ฅ˜) ๊ณ„์‚ฐ ํ•จ์ˆ˜ - String? _getSubCategoryAutocompleteSuggestion(String input) { - if (input.isEmpty) return null; - // ์ž…๋ ฅ๊ฐ’์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ›„๋ณด ์ค‘ ๊ฐ€์žฅ ์งง์€ ๊ฒƒ - final lower = input.toLowerCase(); - final match = _controller.subCategories.firstWhere( - (c) => c.toLowerCase().startsWith(lower), - orElse: () => '', - ); - return match.isNotEmpty && match.length > input.length ? match : null; - } - - // ์ž๋™์™„์„ฑ ํ›„๋ณด(์ž…๋ ฅ๊ฐ’๊ณผ ๊ฐ€์žฅ ๊ทผ์ ‘ํ•œ ์†Œ๋ถ„๋ฅ˜) ๊ณ„์‚ฐ ํ•จ์ˆ˜ - String? _getSubSubCategoryAutocompleteSuggestion(String input) { - if (input.isEmpty) return null; - // ์ž…๋ ฅ๊ฐ’์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ›„๋ณด ์ค‘ ๊ฐ€์žฅ ์งง์€ ๊ฒƒ - final lower = input.toLowerCase(); - final match = _controller.subSubCategories.firstWhere( - (c) => c.toLowerCase().startsWith(lower), - orElse: () => '', - ); - return match.isNotEmpty && match.length > input.length ? match : null; - } @override void initState() { super.initState(); - _controller = EquipmentInFormController( - equipmentInId: widget.equipmentInId, - ); - - print('DEBUG: initState - equipmentInId: ${widget.equipmentInId}, isEditMode: ${_controller.isEditMode}'); - - // ์ปจํŠธ๋กค๋Ÿฌ ๋ณ€๊ฒฝ ๋ฆฌ์Šค๋„ˆ ์ถ”๊ฐ€ (๋ฐ์ดํ„ฐ ๋กœ๋“œ ์ „์— ์ถ”๊ฐ€ํ•ด์•ผ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ์Œ) + _controller = EquipmentInFormController(equipmentInId: widget.equipmentInId); _controller.addListener(_onControllerUpdated); // ์ˆ˜์ • ๋ชจ๋“œ์ผ ๋•Œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ if (_controller.isEditMode) { - print('DEBUG: Edit mode detected, loading equipment data...'); WidgetsBinding.instance.addPostFrameCallback((_) async { await _controller.initializeForEdit(); - print('DEBUG: Equipment data loaded, calling _updateTextControllers directly'); - // ๋ฐ์ดํ„ฐ ๋กœ๋“œ ํ›„ ์ง์ ‘ UI ์—…๋ฐ์ดํŠธ ํ˜ธ์ถœ - if (mounted) { - _updateTextControllers(); - } }); } - - _manufacturerFocusNode = FocusNode(); - _nameFieldFocusNode = FocusNode(); - - // ์ปจํŠธ๋กค๋Ÿฌ๋“ค์„ ๋นˆ ๊ฐ’์œผ๋กœ ์ดˆ๊ธฐํ™” (๋‚˜์ค‘์— ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹œ ์—…๋ฐ์ดํŠธ๋จ) - _partnerController = TextEditingController(); - _warehouseController = TextEditingController(); - _manufacturerController = TextEditingController(); - _equipmentNameController = TextEditingController(); - _categoryController = TextEditingController(); - _subCategoryController = TextEditingController(); - _subSubCategoryController = TextEditingController(); - _nameController = TextEditingController(); - _serialNumberController = TextEditingController(); - _barcodeController = TextEditingController(); - _quantityController = TextEditingController(text: '1'); - _warrantyCodeController = TextEditingController(); - - // ํฌ์ปค์Šค ๋ณ€๊ฒฝ ๋ฆฌ์Šค๋„ˆ ์ถ”๊ฐ€ - _partnerFocusNode.addListener(_onPartnerFocusChange); - _warehouseFocusNode.addListener(_onWarehouseFocusChange); - _manufacturerFocusNode.addListener(_onManufacturerFocusChange); - _nameFieldFocusNode.addListener(_onNameFieldFocusChange); - _categoryFocusNode.addListener(_onCategoryFocusChange); - _subCategoryFocusNode.addListener(_onSubCategoryFocusChange); - _subSubCategoryFocusNode.addListener(_onSubSubCategoryFocusChange); - } - - // ์ปจํŠธ๋กค๋Ÿฌ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ ํ…์ŠคํŠธ ์ปจํŠธ๋กค๋Ÿฌ ์—…๋ฐ์ดํŠธ - void _onControllerUpdated() { - print('DEBUG [_onControllerUpdated] Called - isEditMode: ${_controller.isEditMode}, isLoading: ${_controller.isLoading}, actualEquipmentId: ${_controller.actualEquipmentId}'); - // ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์ด ์™„๋ฃŒ๋˜๊ณ  ์ˆ˜์ • ๋ชจ๋“œ์ผ ๋•Œ ํ…์ŠคํŠธ ์ปจํŠธ๋กค๋Ÿฌ ์—…๋ฐ์ดํŠธ - // actualEquipmentId๊ฐ€ ์„ค์ •๋˜์—ˆ๋‹ค๋Š” ๊ฒƒ์€ ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋“œ๋˜์—ˆ๋‹ค๋Š” ์˜๋ฏธ - if (_controller.isEditMode && !_controller.isLoading && _controller.actualEquipmentId != null) { - print('DEBUG [_onControllerUpdated] Condition met, updating text controllers'); - print('DEBUG [_onControllerUpdated] manufacturer: "${_controller.manufacturer}", name: "${_controller.name}"'); - _updateTextControllers(); - } - } - - // ํ…์ŠคํŠธ ์ปจํŠธ๋กค๋Ÿฌ ์—…๋ฐ์ดํŠธ ๋ฉ”์„œ๋“œ - void _updateTextControllers() { - print('DEBUG [_updateTextControllers] Called'); - print('DEBUG [_updateTextControllers] Before update:'); - print(' manufacturerController.text="${_manufacturerController.text}"'); - print(' nameController.text="${_nameController.text}"'); - print('DEBUG [_updateTextControllers] Controller values:'); - print(' controller.manufacturer="${_controller.manufacturer}"'); - print(' controller.name="${_controller.name}"'); - print(' controller.serialNumber="${_controller.serialNumber}"'); - print(' controller.quantity=${_controller.quantity}'); - - setState(() { - _manufacturerController.text = _controller.manufacturer; - _nameController.text = _controller.name; - _equipmentNameController.text = _controller.name; // ์žฅ๋น„๋ช… ์ปจํŠธ๋กค๋Ÿฌ ์ถ”๊ฐ€ - _categoryController.text = _controller.category; - _subCategoryController.text = _controller.subCategory; - _subSubCategoryController.text = _controller.subSubCategory; - _serialNumberController.text = _controller.serialNumber; - _barcodeController.text = _controller.barcode; - _quantityController.text = _controller.quantity.toString(); - _warehouseController.text = _controller.warehouseLocation ?? ''; - _partnerController.text = _controller.partnerCompany ?? ''; - _warrantyCodeController.text = _controller.warrantyCode ?? ''; - _controller.remarkController.text = _controller.remarkController.text; - }); - - print('DEBUG [_updateTextControllers] After update:'); - print(' manufacturerController.text="${_manufacturerController.text}"'); - print(' nameController.text="${_nameController.text}"'); } @override void dispose() { _controller.removeListener(_onControllerUpdated); - _manufacturerFocusNode.dispose(); - _nameFieldFocusNode.dispose(); - _partnerOverlayEntry?.remove(); - _partnerFocusNode.dispose(); - _partnerController.dispose(); - - // ์ถ”๊ฐ€ ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ - _warehouseOverlayEntry?.remove(); - _warehouseFocusNode.dispose(); - _warehouseController.dispose(); - - _manufacturerOverlayEntry?.remove(); - _manufacturerController.dispose(); - - _equipmentNameOverlayEntry?.remove(); - _equipmentNameController.dispose(); - - _categoryOverlayEntry?.remove(); - _categoryFocusNode.dispose(); - _categoryController.dispose(); - - _subCategoryOverlayEntry?.remove(); - _subCategoryFocusNode.dispose(); - _subCategoryController.dispose(); - - _subSubCategoryOverlayEntry?.remove(); - _subSubCategoryFocusNode.dispose(); - _subSubCategoryController.dispose(); - - // ์ถ”๊ฐ€ ์ปจํŠธ๋กค๋Ÿฌ ์ •๋ฆฌ - _nameController.dispose(); - _serialNumberController.dispose(); - _barcodeController.dispose(); - _quantityController.dispose(); - _warrantyCodeController.dispose(); - _controller.dispose(); super.dispose(); } - /// ์œ ํšจํ•œ ์žฅ๋น„ ์ƒํƒœ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ - String? _getValidEquipmentStatus(String? status) { - const validStatuses = ['available', 'inuse', 'maintenance', 'disposed']; - return validStatuses.contains(status) ? status : null; + void _onControllerUpdated() { + if (mounted) setState(() {}); } - // ํฌ์ปค์Šค ๋ณ€๊ฒฝ ๋ฆฌ์Šค๋„ˆ ํ•จ์ˆ˜๋“ค - void _onPartnerFocusChange() { - if (!_partnerFocusNode.hasFocus) { - // ํฌ์ปค์Šค๊ฐ€ ๋ฒ—์–ด๋‚˜๋ฉด ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removePartnerDropdown(); - } else { - // ๋‹ค๋ฅธ ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeOtherDropdowns(_partnerOverlayEntry); - } - } - - void _onWarehouseFocusChange() { - if (!_warehouseFocusNode.hasFocus) { - // ํฌ์ปค์Šค๊ฐ€ ๋ฒ—์–ด๋‚˜๋ฉด ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeWarehouseDropdown(); - } else { - // ๋‹ค๋ฅธ ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeOtherDropdowns(_warehouseOverlayEntry); - } - } - - void _onManufacturerFocusChange() { - if (!_manufacturerFocusNode.hasFocus) { - // ํฌ์ปค์Šค๊ฐ€ ๋ฒ—์–ด๋‚˜๋ฉด ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeManufacturerDropdown(); - } else { - // ๋‹ค๋ฅธ ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeOtherDropdowns(_manufacturerOverlayEntry); - } - } - - void _onNameFieldFocusChange() { - if (!_nameFieldFocusNode.hasFocus) { - // ํฌ์ปค์Šค๊ฐ€ ๋ฒ—์–ด๋‚˜๋ฉด ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeEquipmentNameDropdown(); - } else { - // ๋‹ค๋ฅธ ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeOtherDropdowns(_equipmentNameOverlayEntry); - } - } - - void _onCategoryFocusChange() { - if (!_categoryFocusNode.hasFocus) { - // ํฌ์ปค์Šค๊ฐ€ ๋ฒ—์–ด๋‚˜๋ฉด ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeCategoryDropdown(); - } else { - // ๋‹ค๋ฅธ ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeOtherDropdowns(_categoryOverlayEntry); - } - } - - void _onSubCategoryFocusChange() { - if (!_subCategoryFocusNode.hasFocus) { - // ํฌ์ปค์Šค๊ฐ€ ๋ฒ—์–ด๋‚˜๋ฉด ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeSubCategoryDropdown(); - } else { - // ๋‹ค๋ฅธ ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeOtherDropdowns(_subCategoryOverlayEntry); - } - } - - void _onSubSubCategoryFocusChange() { - if (!_subSubCategoryFocusNode.hasFocus) { - // ํฌ์ปค์Šค๊ฐ€ ๋ฒ—์–ด๋‚˜๋ฉด ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeSubSubCategoryDropdown(); - } else { - // ๋‹ค๋ฅธ ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeOtherDropdowns(_subSubCategoryOverlayEntry); - } - } - - // ํ˜„์žฌ ํฌ์ปค์Šค ํ•„๋“œ ์™ธ์˜ ๋‹ค๋ฅธ ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ์ œ๊ฑฐ - void _removeOtherDropdowns(OverlayEntry? currentOverlay) { - // ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ์ค‘ ํ˜„์žฌ ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ ๋‹ซ๊ธฐ - if (_partnerOverlayEntry != null && - _partnerOverlayEntry != currentOverlay) { - _removePartnerDropdown(); - } - if (_warehouseOverlayEntry != null && - _warehouseOverlayEntry != currentOverlay) { - _removeWarehouseDropdown(); - } - if (_manufacturerOverlayEntry != null && - _manufacturerOverlayEntry != currentOverlay) { - _removeManufacturerDropdown(); - } - if (_equipmentNameOverlayEntry != null && - _equipmentNameOverlayEntry != currentOverlay) { - _removeEquipmentNameDropdown(); - } - if (_categoryOverlayEntry != null && - _categoryOverlayEntry != currentOverlay) { - _removeCategoryDropdown(); - } - if (_subCategoryOverlayEntry != null && - _subCategoryOverlayEntry != currentOverlay) { - _removeSubCategoryDropdown(); - } - if (_subSubCategoryOverlayEntry != null && - _subSubCategoryOverlayEntry != currentOverlay) { - _removeSubSubCategoryDropdown(); - } - } - - Future _saveEquipmentIn() async { - // ๋กœ๋”ฉ ๋‹ค์ด์–ผ๋กœ๊ทธ ํ‘œ์‹œ - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => const Center( - child: CircularProgressIndicator(), - ), - ); + // ์œ ํšจํ•œ ์ œ์กฐ์‚ฌ ๊ฐ’ ๋ฐ˜ํ™˜ (๋“œ๋กญ๋‹ค์šด assertion ์˜ค๋ฅ˜ ๋ฐฉ์ง€) + String? _getValidManufacturer() { + if (_controller.manufacturer.isEmpty) return null; - try { - final success = await _controller.save(); - - // ๋กœ๋”ฉ ๋‹ค์ด์–ผ๋กœ๊ทธ ๋‹ซ๊ธฐ - if (!mounted) return; - Navigator.pop(context); - - if (success) { - // ์„ฑ๊ณต ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(_controller.isEditMode ? '์žฅ๋น„ ์ •๋ณด๊ฐ€ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.' : '์žฅ๋น„ ์ž…๊ณ ๊ฐ€ ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'), - backgroundColor: Colors.green, - ), - ); - if (!mounted) return; - Navigator.pop(context, true); - } else { - // ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(_controller.error ?? '์ €์žฅ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.'), - backgroundColor: Colors.red, - ), - ); - } - } catch (e) { - // ๋กœ๋”ฉ ๋‹ค์ด์–ผ๋กœ๊ทธ ๋‹ซ๊ธฐ - if (!mounted) return; - Navigator.pop(context); - - // ์˜ˆ์™ธ ์ฒ˜๋ฆฌ - if (!mounted) return; + final isValid = _controller.manufacturers.contains(_controller.manufacturer); + print('DEBUG [_getValidManufacturer] manufacturer: "${_controller.manufacturer}", isValid: $isValid, available: ${_controller.manufacturers.take(5).toList()}'); + + return isValid ? _controller.manufacturer : null; + } + + // ์œ ํšจํ•œ ๋ชจ๋ธ๋ช… ๊ฐ’ ๋ฐ˜ํ™˜ (๋“œ๋กญ๋‹ค์šด assertion ์˜ค๋ฅ˜ ๋ฐฉ์ง€) + String? _getValidModelName() { + if (_controller.modelName.isEmpty) return null; + + final isValid = _controller.equipmentNames.contains(_controller.modelName); + print('DEBUG [_getValidModelName] modelName: "${_controller.modelName}", isValid: $isValid, available: ${_controller.equipmentNames.take(5).toList()}'); + + return isValid ? _controller.modelName : null; + } + + // ์œ ํšจํ•œ ๊ตฌ๋งค์ฒ˜ ID ๋ฐ˜ํ™˜ (๋“œ๋กญ๋‹ค์šด assertion ์˜ค๋ฅ˜ ๋ฐฉ์ง€) + int? _getValidCompanyId() { + if (_controller.selectedCompanyId == null) return null; + + final isValid = _controller.companies.containsKey(_controller.selectedCompanyId); + print('DEBUG [_getValidCompanyId] selectedCompanyId: ${_controller.selectedCompanyId}, isValid: $isValid, available companies: ${_controller.companies.length}'); + + return isValid ? _controller.selectedCompanyId : null; + } + + // ์œ ํšจํ•œ ์ฐฝ๊ณ  ID ๋ฐ˜ํ™˜ (๋“œ๋กญ๋‹ค์šด assertion ์˜ค๋ฅ˜ ๋ฐฉ์ง€) + int? _getValidWarehouseId() { + if (_controller.selectedWarehouseId == null) return null; + + final isValid = _controller.warehouses.containsKey(_controller.selectedWarehouseId); + print('DEBUG [_getValidWarehouseId] selectedWarehouseId: ${_controller.selectedWarehouseId}, isValid: $isValid, available warehouses: ${_controller.warehouses.length}'); + + return isValid ? _controller.selectedWarehouseId : null; + } + + Future _onSave() async { + if (_controller.isSaving) return; + + final success = await _controller.save(); + if (success && mounted) { + Navigator.pop(context, true); + } else if (_controller.error != null && mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('์˜ค๋ฅ˜: $e'), + content: Text(_controller.error!), backgroundColor: Colors.red, ), ); } } - void _showPartnerDropdown() { - // ํ•ญ์ƒ ๊ธฐ์กด ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ๋จผ์ € ์ œ๊ฑฐํ•˜์—ฌ ์ค‘๋ณต ์ƒ์„ฑ ๋ฐฉ์ง€ - _removePartnerDropdown(); - // ๋‹ค๋ฅธ ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeOtherDropdowns(_partnerOverlayEntry); - // ์ž…๋ ฅ๋ž€์˜ ์ •ํ™•ํ•œ RenderBox๋ฅผ key๋กœ๋ถ€ํ„ฐ ์ฐธ์กฐ - final RenderBox renderBox = - _partnerFieldKey.currentContext!.findRenderObject() as RenderBox; - final size = renderBox.size; - print('[๊ตฌ๋งค์ฒ˜:showPartnerDropdown] ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ, width=${size.width}'); - final itemsToShow = _controller.partnerCompanies; - print('[๊ตฌ๋งค์ฒ˜:showPartnerDropdown] ๋“œ๋กญ๋‹ค์šด์— ๋…ธ์ถœ๋  ์•„์ดํ…œ: $itemsToShow'); - _partnerOverlayEntry = OverlayEntry( - builder: - (context) => Positioned( - width: size.width, - child: CompositedTransformFollower( - link: _partnerLayerLink, - showWhenUnlinked: false, - offset: const Offset(0, 45), - child: Material( - elevation: 4, - borderRadius: BorderRadius.circular(4), - child: Container( - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(4), - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues(alpha: 0.3), - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), - ), - ], - ), - constraints: const BoxConstraints(maxHeight: 200), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ...itemsToShow.map((item) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - print( - '[๊ตฌ๋งค์ฒ˜:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] ์„ ํƒ๊ฐ’: "$item" (๊ธธ์ด: ${item.length})', - ); - if (item.isEmpty) { - print('[๊ตฌ๋งค์ฒ˜:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] ๊ฒฝ๊ณ : ๋นˆ ๊ฐ’์ด ์„ ํƒ๋จ!'); - } - setState(() { - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ ์‹œ์ž‘ - _isProgrammaticPartnerChange = true; - print( - '[๊ตฌ๋งค์ฒ˜:setState:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ] _controller.partnerCompany <- "$item"', - ); - _controller.partnerCompany = item; - print( - '[๊ตฌ๋งค์ฒ˜:setState:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ] _partnerController.text <- "$item"', - ); - _partnerController.text = item; - }); - print( - '[๊ตฌ๋งค์ฒ˜:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] setState ์ดํ›„ _partnerController.text=${_partnerController.text}, _controller.partnerCompany=${_controller.partnerCompany}', - ); - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ ์ข…๋ฃŒ (๋‹ค์Œ ํ”„๋ ˆ์ž„์—์„œ) - WidgetsBinding.instance.addPostFrameCallback((_) { - _isProgrammaticPartnerChange = false; - }); - _removePartnerDropdown(); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - width: double.infinity, - child: Text(item), - ), - ); - }), - ], - ), - ), - ), - ), - ), - ), - ); - Overlay.of(context).insert(_partnerOverlayEntry!); - } - - void _removePartnerDropdown() { - // ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์žˆ์œผ๋ฉด ์ •์ƒ์ ์œผ๋กœ ์ œ๊ฑฐ ๋ฐ null ์ฒ˜๋ฆฌ - if (_partnerOverlayEntry != null) { - _partnerOverlayEntry!.remove(); - _partnerOverlayEntry = null; - print('[๊ตฌ๋งค์ฒ˜:removePartnerDropdown] ์˜ค๋ฒ„๋ ˆ์ด ์ œ๊ฑฐ ์™„๋ฃŒ'); - } - } - - // ์ž…๊ณ ์ง€ ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ ํ•จ์ˆ˜ - void _showWarehouseDropdown() { - // ํ•ญ์ƒ ๊ธฐ์กด ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ๋จผ์ € ์ œ๊ฑฐํ•˜์—ฌ ์ค‘๋ณต ์ƒ์„ฑ ๋ฐฉ์ง€ - _removeWarehouseDropdown(); - // ๋‹ค๋ฅธ ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeOtherDropdowns(_warehouseOverlayEntry); - // ์ž…๋ ฅ๋ž€์˜ ์ •ํ™•ํ•œ RenderBox๋ฅผ key๋กœ๋ถ€ํ„ฐ ์ฐธ์กฐ - final RenderBox renderBox = - _warehouseFieldKey.currentContext!.findRenderObject() as RenderBox; - final size = renderBox.size; - print('[์ž…๊ณ ์ง€:showWarehouseDropdown] ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ, width=${size.width}'); - final itemsToShow = _controller.warehouseLocations; - print('[์ž…๊ณ ์ง€:showWarehouseDropdown] ๋“œ๋กญ๋‹ค์šด์— ๋…ธ์ถœ๋  ์•„์ดํ…œ: $itemsToShow'); - _warehouseOverlayEntry = OverlayEntry( - builder: - (context) => Positioned( - width: size.width, - child: CompositedTransformFollower( - link: _warehouseLayerLink, - showWhenUnlinked: false, - offset: const Offset(0, 45), - child: Material( - elevation: 4, - borderRadius: BorderRadius.circular(4), - child: Container( - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(4), - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues(alpha: 0.3), - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), - ), - ], - ), - constraints: const BoxConstraints(maxHeight: 200), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ...itemsToShow.map((item) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - print( - '[์ž…๊ณ ์ง€:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] ์„ ํƒ๊ฐ’: "$item" (๊ธธ์ด: ${item.length})', - ); - if (item.isEmpty) { - print('[์ž…๊ณ ์ง€:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] ๊ฒฝ๊ณ : ๋นˆ ๊ฐ’์ด ์„ ํƒ๋จ!'); - } - setState(() { - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ ์‹œ์ž‘ - _isProgrammaticWarehouseChange = true; - print( - '[์ž…๊ณ ์ง€:setState:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ] _controller.warehouseLocation <- "$item"', - ); - _controller.warehouseLocation = item; - print( - '[์ž…๊ณ ์ง€:setState:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ] _warehouseController.text <- "$item"', - ); - _warehouseController.text = item; - }); - print( - '[์ž…๊ณ ์ง€:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] setState ์ดํ›„ _warehouseController.text=${_warehouseController.text}, _controller.warehouseLocation=${_controller.warehouseLocation}', - ); - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ ์ข…๋ฃŒ (๋‹ค์Œ ํ”„๋ ˆ์ž„์—์„œ) - WidgetsBinding.instance.addPostFrameCallback((_) { - _isProgrammaticWarehouseChange = false; - }); - _removeWarehouseDropdown(); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - width: double.infinity, - child: Text(item), - ), - ); - }), - ], - ), - ), - ), - ), - ), - ), - ); - Overlay.of(context).insert(_warehouseOverlayEntry!); - } - - void _removeWarehouseDropdown() { - // ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์žˆ์œผ๋ฉด ์ •์ƒ์ ์œผ๋กœ ์ œ๊ฑฐ ๋ฐ null ์ฒ˜๋ฆฌ - if (_warehouseOverlayEntry != null) { - _warehouseOverlayEntry!.remove(); - _warehouseOverlayEntry = null; - print('[์ž…๊ณ ์ง€:removeWarehouseDropdown] ์˜ค๋ฒ„๋ ˆ์ด ์ œ๊ฑฐ ์™„๋ฃŒ'); - } - } - - // ์ œ์กฐ์‚ฌ ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ ํ•จ์ˆ˜ - void _showManufacturerDropdown() { - // ํ•ญ์ƒ ๊ธฐ์กด ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ๋จผ์ € ์ œ๊ฑฐํ•˜์—ฌ ์ค‘๋ณต ์ƒ์„ฑ ๋ฐฉ์ง€ - _removeManufacturerDropdown(); - // ๋‹ค๋ฅธ ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeOtherDropdowns(_manufacturerOverlayEntry); - // ์ž…๋ ฅ๋ž€์˜ ์ •ํ™•ํ•œ RenderBox๋ฅผ key๋กœ๋ถ€ํ„ฐ ์ฐธ์กฐ - final RenderBox renderBox = - _manufacturerFieldKey.currentContext!.findRenderObject() as RenderBox; - final size = renderBox.size; - print('[์ œ์กฐ์‚ฌ:showManufacturerDropdown] ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ, width=${size.width}'); - final itemsToShow = _controller.manufacturers; - print('[์ œ์กฐ์‚ฌ:showManufacturerDropdown] ๋“œ๋กญ๋‹ค์šด์— ๋…ธ์ถœ๋  ์•„์ดํ…œ: $itemsToShow'); - _manufacturerOverlayEntry = OverlayEntry( - builder: - (context) => Positioned( - width: size.width, - child: CompositedTransformFollower( - link: _manufacturerLayerLink, - showWhenUnlinked: false, - offset: const Offset(0, 45), - child: Material( - elevation: 4, - borderRadius: BorderRadius.circular(4), - child: Container( - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(4), - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues(alpha: 0.3), - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), - ), - ], - ), - constraints: const BoxConstraints(maxHeight: 200), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ...itemsToShow.map((item) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - print( - '[์ œ์กฐ์‚ฌ:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] ์„ ํƒ๊ฐ’: "$item" (๊ธธ์ด: ${item.length})', - ); - if (item.isEmpty) { - print('[์ œ์กฐ์‚ฌ:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] ๊ฒฝ๊ณ : ๋นˆ ๊ฐ’์ด ์„ ํƒ๋จ!'); - } - setState(() { - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ ์‹œ์ž‘ - _isProgrammaticManufacturerChange = true; - print( - '[์ œ์กฐ์‚ฌ:setState:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ] _controller.manufacturer <- "$item"', - ); - _controller.manufacturer = item; - print( - '[์ œ์กฐ์‚ฌ:setState:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ] _manufacturerController.text <- "$item"', - ); - _manufacturerController.text = item; - }); - print( - '[์ œ์กฐ์‚ฌ:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] setState ์ดํ›„ _manufacturerController.text=${_manufacturerController.text}, _controller.manufacturer=${_controller.manufacturer}', - ); - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ ์ข…๋ฃŒ (๋‹ค์Œ ํ”„๋ ˆ์ž„์—์„œ) - WidgetsBinding.instance.addPostFrameCallback((_) { - _isProgrammaticManufacturerChange = false; - }); - _removeManufacturerDropdown(); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - width: double.infinity, - child: Text(item), - ), - ); - }), - ], - ), - ), - ), - ), - ), - ), - ); - Overlay.of(context).insert(_manufacturerOverlayEntry!); - } - - void _removeManufacturerDropdown() { - // ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์žˆ์œผ๋ฉด ์ •์ƒ์ ์œผ๋กœ ์ œ๊ฑฐ ๋ฐ null ์ฒ˜๋ฆฌ - if (_manufacturerOverlayEntry != null) { - _manufacturerOverlayEntry!.remove(); - _manufacturerOverlayEntry = null; - print('[์ œ์กฐ์‚ฌ:removeManufacturerDropdown] ์˜ค๋ฒ„๋ ˆ์ด ์ œ๊ฑฐ ์™„๋ฃŒ'); - } - } - - // ์žฅ๋น„๋ช… ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ ํ•จ์ˆ˜ - void _showEquipmentNameDropdown() { - // ํ•ญ์ƒ ๊ธฐ์กด ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ๋จผ์ € ์ œ๊ฑฐํ•˜์—ฌ ์ค‘๋ณต ์ƒ์„ฑ ๋ฐฉ์ง€ - _removeEquipmentNameDropdown(); - // ๋‹ค๋ฅธ ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeOtherDropdowns(_equipmentNameOverlayEntry); - // ์ž…๋ ฅ๋ž€์˜ ์ •ํ™•ํ•œ RenderBox๋ฅผ key๋กœ๋ถ€ํ„ฐ ์ฐธ์กฐ - final RenderBox renderBox = - _equipmentNameFieldKey.currentContext!.findRenderObject() as RenderBox; - final size = renderBox.size; - print('[์žฅ๋น„๋ช…:showEquipmentNameDropdown] ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ, width=${size.width}'); - final itemsToShow = _controller.equipmentNames; - print('[์žฅ๋น„๋ช…:showEquipmentNameDropdown] ๋“œ๋กญ๋‹ค์šด์— ๋…ธ์ถœ๋  ์•„์ดํ…œ: $itemsToShow'); - _equipmentNameOverlayEntry = OverlayEntry( - builder: - (context) => Positioned( - width: size.width, - child: CompositedTransformFollower( - link: _equipmentNameLayerLink, - showWhenUnlinked: false, - offset: const Offset(0, 45), - child: Material( - elevation: 4, - borderRadius: BorderRadius.circular(4), - child: Container( - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(4), - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues(alpha: 0.3), - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), - ), - ], - ), - constraints: const BoxConstraints(maxHeight: 200), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ...itemsToShow.map((item) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - print( - '[์žฅ๋น„๋ช…:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] ์„ ํƒ๊ฐ’: "$item" (๊ธธ์ด: ${item.length})', - ); - if (item.isEmpty) { - print('[์žฅ๋น„๋ช…:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] ๊ฒฝ๊ณ : ๋นˆ ๊ฐ’์ด ์„ ํƒ๋จ!'); - } - setState(() { - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ ์‹œ์ž‘ - _isProgrammaticEquipmentNameChange = true; - print( - '[์žฅ๋น„๋ช…:setState:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ] _controller.name <- "$item"', - ); - _controller.name = item; - print( - '[์žฅ๋น„๋ช…:setState:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ] _equipmentNameController.text <- "$item"', - ); - _equipmentNameController.text = item; - }); - print( - '[์žฅ๋น„๋ช…:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] setState ์ดํ›„ _equipmentNameController.text=${_equipmentNameController.text}, _controller.name=${_controller.name}', - ); - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ ์ข…๋ฃŒ (๋‹ค์Œ ํ”„๋ ˆ์ž„์—์„œ) - WidgetsBinding.instance.addPostFrameCallback((_) { - _isProgrammaticEquipmentNameChange = false; - }); - _removeEquipmentNameDropdown(); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - width: double.infinity, - child: Text(item), - ), - ); - }), - ], - ), - ), - ), - ), - ), - ), - ); - Overlay.of(context).insert(_equipmentNameOverlayEntry!); - } - - void _removeEquipmentNameDropdown() { - // ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์žˆ์œผ๋ฉด ์ •์ƒ์ ์œผ๋กœ ์ œ๊ฑฐ ๋ฐ null ์ฒ˜๋ฆฌ - if (_equipmentNameOverlayEntry != null) { - _equipmentNameOverlayEntry!.remove(); - _equipmentNameOverlayEntry = null; - print('[์žฅ๋น„๋ช…:removeEquipmentNameDropdown] ์˜ค๋ฒ„๋ ˆ์ด ์ œ๊ฑฐ ์™„๋ฃŒ'); - } - } - - // ๋Œ€๋ถ„๋ฅ˜ ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ ํ•จ์ˆ˜ - void _showCategoryDropdown() { - // ํ•ญ์ƒ ๊ธฐ์กด ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ๋จผ์ € ์ œ๊ฑฐํ•˜์—ฌ ์ค‘๋ณต ์ƒ์„ฑ ๋ฐฉ์ง€ - _removeCategoryDropdown(); - // ๋‹ค๋ฅธ ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeOtherDropdowns(_categoryOverlayEntry); - // ์ž…๋ ฅ๋ž€์˜ ์ •ํ™•ํ•œ RenderBox๋ฅผ key๋กœ๋ถ€ํ„ฐ ์ฐธ์กฐ - final RenderBox renderBox = - _categoryFieldKey.currentContext!.findRenderObject() as RenderBox; - final size = renderBox.size; - print('[๋Œ€๋ถ„๋ฅ˜:showCategoryDropdown] ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ, width=${size.width}'); - final itemsToShow = _controller.categories; - print('[๋Œ€๋ถ„๋ฅ˜:showCategoryDropdown] ๋“œ๋กญ๋‹ค์šด์— ๋…ธ์ถœ๋  ์•„์ดํ…œ: $itemsToShow'); - _categoryOverlayEntry = OverlayEntry( - builder: - (context) => Positioned( - width: size.width, - child: CompositedTransformFollower( - link: _categoryLayerLink, - showWhenUnlinked: false, - offset: const Offset(0, 45), - child: Material( - elevation: 4, - borderRadius: BorderRadius.circular(4), - child: Container( - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(4), - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues(alpha: 0.3), - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), - ), - ], - ), - constraints: const BoxConstraints(maxHeight: 200), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ...itemsToShow.map((item) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - print( - '[๋Œ€๋ถ„๋ฅ˜:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] ์„ ํƒ๊ฐ’: "$item" (๊ธธ์ด: ${item.length})', - ); - if (item.isEmpty) { - print('[๋Œ€๋ถ„๋ฅ˜:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] ๊ฒฝ๊ณ : ๋นˆ ๊ฐ’์ด ์„ ํƒ๋จ!'); - } - setState(() { - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ ์‹œ์ž‘ - _isProgrammaticCategoryChange = true; - print( - '[๋Œ€๋ถ„๋ฅ˜:setState:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ] _controller.category <- "$item"', - ); - _controller.category = item; - print( - '[๋Œ€๋ถ„๋ฅ˜:setState:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ] _categoryController.text <- "$item"', - ); - _categoryController.text = item; - }); - print( - '[๋Œ€๋ถ„๋ฅ˜:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] setState ์ดํ›„ _categoryController.text=${_categoryController.text}, _controller.category=${_controller.category}', - ); - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ ์ข…๋ฃŒ (๋‹ค์Œ ํ”„๋ ˆ์ž„์—์„œ) - WidgetsBinding.instance.addPostFrameCallback((_) { - _isProgrammaticCategoryChange = false; - }); - _removeCategoryDropdown(); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - width: double.infinity, - child: Text(item), - ), - ); - }), - ], - ), - ), - ), - ), - ), - ), - ); - Overlay.of(context).insert(_categoryOverlayEntry!); - } - - void _removeCategoryDropdown() { - // ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์žˆ์œผ๋ฉด ์ •์ƒ์ ์œผ๋กœ ์ œ๊ฑฐ ๋ฐ null ์ฒ˜๋ฆฌ - if (_categoryOverlayEntry != null) { - _categoryOverlayEntry!.remove(); - _categoryOverlayEntry = null; - print('[๋Œ€๋ถ„๋ฅ˜:removeCategoryDropdown] ์˜ค๋ฒ„๋ ˆ์ด ์ œ๊ฑฐ ์™„๋ฃŒ'); - } - } - - // ์ค‘๋ถ„๋ฅ˜ ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ ํ•จ์ˆ˜ - void _showSubCategoryDropdown() { - // ํ•ญ์ƒ ๊ธฐ์กด ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ๋จผ์ € ์ œ๊ฑฐํ•˜์—ฌ ์ค‘๋ณต ์ƒ์„ฑ ๋ฐฉ์ง€ - _removeSubCategoryDropdown(); - // ๋‹ค๋ฅธ ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeOtherDropdowns(_subCategoryOverlayEntry); - // ์ž…๋ ฅ๋ž€์˜ ์ •ํ™•ํ•œ RenderBox๋ฅผ key๋กœ๋ถ€ํ„ฐ ์ฐธ์กฐ - final RenderBox renderBox = - _subCategoryFieldKey.currentContext!.findRenderObject() as RenderBox; - final size = renderBox.size; - print('[์ค‘๋ถ„๋ฅ˜:showSubCategoryDropdown] ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ, width=${size.width}'); - final itemsToShow = _controller.subCategories; - print('[์ค‘๋ถ„๋ฅ˜:showSubCategoryDropdown] ๋“œ๋กญ๋‹ค์šด์— ๋…ธ์ถœ๋  ์•„์ดํ…œ: $itemsToShow'); - _subCategoryOverlayEntry = OverlayEntry( - builder: - (context) => Positioned( - width: size.width, - child: CompositedTransformFollower( - link: _subCategoryLayerLink, - showWhenUnlinked: false, - offset: const Offset(0, 45), - child: Material( - elevation: 4, - borderRadius: BorderRadius.circular(4), - child: Container( - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(4), - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues(alpha: 0.3), - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), - ), - ], - ), - constraints: const BoxConstraints(maxHeight: 200), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ...itemsToShow.map((item) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - print( - '[์ค‘๋ถ„๋ฅ˜:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] ์„ ํƒ๊ฐ’: "$item" (๊ธธ์ด: ${item.length})', - ); - if (item.isEmpty) { - print('[์ค‘๋ถ„๋ฅ˜:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] ๊ฒฝ๊ณ : ๋นˆ ๊ฐ’์ด ์„ ํƒ๋จ!'); - } - setState(() { - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ ์‹œ์ž‘ - _isProgrammaticSubCategoryChange = true; - print( - '[์ค‘๋ถ„๋ฅ˜:setState:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ] _controller.subCategory <- "$item"', - ); - _controller.subCategory = item; - print( - '[์ค‘๋ถ„๋ฅ˜:setState:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ] _subCategoryController.text <- "$item"', - ); - _subCategoryController.text = item; - }); - print( - '[์ค‘๋ถ„๋ฅ˜:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] setState ์ดํ›„ _subCategoryController.text=${_subCategoryController.text}, _controller.subCategory=${_controller.subCategory}', - ); - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ ์ข…๋ฃŒ (๋‹ค์Œ ํ”„๋ ˆ์ž„์—์„œ) - WidgetsBinding.instance.addPostFrameCallback((_) { - _isProgrammaticSubCategoryChange = false; - }); - _removeSubCategoryDropdown(); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - width: double.infinity, - child: Text(item), - ), - ); - }), - ], - ), - ), - ), - ), - ), - ), - ); - Overlay.of(context).insert(_subCategoryOverlayEntry!); - } - - void _removeSubCategoryDropdown() { - // ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์žˆ์œผ๋ฉด ์ •์ƒ์ ์œผ๋กœ ์ œ๊ฑฐ ๋ฐ null ์ฒ˜๋ฆฌ - if (_subCategoryOverlayEntry != null) { - _subCategoryOverlayEntry!.remove(); - _subCategoryOverlayEntry = null; - print('[์ค‘๋ถ„๋ฅ˜:removeSubCategoryDropdown] ์˜ค๋ฒ„๋ ˆ์ด ์ œ๊ฑฐ ์™„๋ฃŒ'); - } - } - - // ์†Œ๋ถ„๋ฅ˜ ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ ํ•จ์ˆ˜ - void _showSubSubCategoryDropdown() { - // ํ•ญ์ƒ ๊ธฐ์กด ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ๋จผ์ € ์ œ๊ฑฐํ•˜์—ฌ ์ค‘๋ณต ์ƒ์„ฑ ๋ฐฉ์ง€ - _removeSubSubCategoryDropdown(); - // ๋‹ค๋ฅธ ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removeOtherDropdowns(_subSubCategoryOverlayEntry); - // ์ž…๋ ฅ๋ž€์˜ ์ •ํ™•ํ•œ RenderBox๋ฅผ key๋กœ๋ถ€ํ„ฐ ์ฐธ์กฐ - final RenderBox renderBox = - _subSubCategoryFieldKey.currentContext!.findRenderObject() as RenderBox; - final size = renderBox.size; - print('[์†Œ๋ถ„๋ฅ˜:showSubSubCategoryDropdown] ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ, width=${size.width}'); - final itemsToShow = _controller.subSubCategories; - print('[์†Œ๋ถ„๋ฅ˜:showSubSubCategoryDropdown] ๋“œ๋กญ๋‹ค์šด์— ๋…ธ์ถœ๋  ์•„์ดํ…œ: $itemsToShow'); - _subSubCategoryOverlayEntry = OverlayEntry( - builder: - (context) => Positioned( - width: size.width, - child: CompositedTransformFollower( - link: _subSubCategoryLayerLink, - showWhenUnlinked: false, - offset: const Offset(0, 45), - child: Material( - elevation: 4, - borderRadius: BorderRadius.circular(4), - child: Container( - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(4), - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues(alpha: 0.3), - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), - ), - ], - ), - constraints: const BoxConstraints(maxHeight: 200), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ...itemsToShow.map((item) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - print( - '[์†Œ๋ถ„๋ฅ˜:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] ์„ ํƒ๊ฐ’: "$item" (๊ธธ์ด: ${item.length})', - ); - if (item.isEmpty) { - print('[์†Œ๋ถ„๋ฅ˜:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] ๊ฒฝ๊ณ : ๋นˆ ๊ฐ’์ด ์„ ํƒ๋จ!'); - } - setState(() { - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ ์‹œ์ž‘ - _isProgrammaticSubSubCategoryChange = true; - print( - '[์†Œ๋ถ„๋ฅ˜:setState:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ] _controller.subSubCategory <- "$item"', - ); - _controller.subSubCategory = item; - print( - '[์†Œ๋ถ„๋ฅ˜:setState:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ] _subSubCategoryController.text <- "$item"', - ); - _subSubCategoryController.text = item; - }); - print( - '[์†Œ๋ถ„๋ฅ˜:๋“œ๋กญ๋‹ค์šด์•„์ดํ…œ:ํด๋ฆญ] setState ์ดํ›„ _subSubCategoryController.text=${_subSubCategoryController.text}, _controller.subSubCategory=${_controller.subSubCategory}', - ); - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ ์ข…๋ฃŒ (๋‹ค์Œ ํ”„๋ ˆ์ž„์—์„œ) - WidgetsBinding.instance.addPostFrameCallback((_) { - _isProgrammaticSubSubCategoryChange = false; - }); - _removeSubSubCategoryDropdown(); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - width: double.infinity, - child: Text(item), - ), - ); - }), - ], - ), - ), - ), - ), - ), - ), - ); - Overlay.of(context).insert(_subSubCategoryOverlayEntry!); - } - - void _removeSubSubCategoryDropdown() { - // ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์žˆ์œผ๋ฉด ์ •์ƒ์ ์œผ๋กœ ์ œ๊ฑฐ ๋ฐ null ์ฒ˜๋ฆฌ - if (_subSubCategoryOverlayEntry != null) { - _subSubCategoryOverlayEntry!.remove(); - _subSubCategoryOverlayEntry = null; - print('[์†Œ๋ถ„๋ฅ˜:removeSubSubCategoryDropdown] ์˜ค๋ฒ„๋ ˆ์ด ์ œ๊ฑฐ ์™„๋ฃŒ'); - } - } - @override Widget build(BuildContext context) { - print( - '[๊ตฌ๋งค์ฒ˜:build] _partnerController.text=${_partnerController.text}, _controller.partnerCompany=${_controller.partnerCompany}', - ); - final inputText = _partnerController.text; - final suggestion = _getAutocompleteSuggestion(inputText); - final showSuggestion = - suggestion != null && suggestion.length > inputText.length; - print( - '[๊ตฌ๋งค์ฒ˜:autocomplete] ์ž…๋ ฅ๊ฐ’: "$inputText", ์ž๋™์™„์„ฑ ํ›„๋ณด: "$suggestion", showSuggestion=$showSuggestion', - ); - return ChangeNotifierProvider.value( - value: _controller, - child: Consumer( - builder: (context, controller, child) { - // ์ˆ˜์ • ๋ชจ๋“œ์—์„œ ๋กœ๋”ฉ ์ค‘์ผ ๋•Œ ๋กœ๋”ฉ ์ธ๋””์ผ€์ดํ„ฐ ํ‘œ์‹œ - if (controller.isEditMode && controller.isLoading) { - return Scaffold( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - body: const Center( + // ๊ฐ„์†Œํ™”๋œ ๋””๋ฒ„๊น… + print('๐ŸŽฏ [UI] canSave: ${_controller.canSave} | ์žฅ๋น„๋ฒˆํ˜ธ: "${_controller.equipmentNumber}" | ์ œ์กฐ์‚ฌ: "${_controller.manufacturer}"'); + + return FormLayoutTemplate( + title: _controller.isEditMode ? '์žฅ๋น„ ์ˆ˜์ •' : '์žฅ๋น„ ์ž…๊ณ ', + onSave: _controller.canSave && !_controller.isSaving ? _onSave : null, + onCancel: () => Navigator.of(context).pop(), + isLoading: _controller.isSaving, + child: _controller.isLoading + ? const Center(child: CircularProgressIndicator()) + : Form( + key: _controller.formKey, + child: SingleChildScrollView( + padding: const EdgeInsets.only(bottom: 24), child: Column( - mainAxisAlignment: MainAxisAlignment.center, children: [ - CircularProgressIndicator(), - SizedBox(height: 16), - Text('์žฅ๋น„ ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...'), + _buildBasicFields(), + const SizedBox(height: 24), + _buildCategorySection(), + const SizedBox(height: 24), + _buildLocationSection(), + const SizedBox(height: 24), + _buildPurchaseSection(), + const SizedBox(height: 24), + _buildRemarkSection(), ], ), ), - ); - } - - return GestureDetector( - // ํ™”๋ฉด์˜ ๋‹ค๋ฅธ ๊ณณ์„ ํƒญํ•˜๋ฉด ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - onTap: () { - // ํ˜„์žฌ ํฌ์ปค์Šค๋œ ์œ„์ ฏ ํฌ์ปค์Šค ํ•ด์ œ - FocusScope.of(context).unfocus(); - // ๋ชจ๋“  ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ - _removePartnerDropdown(); - _removeWarehouseDropdown(); - _removeManufacturerDropdown(); - _removeEquipmentNameDropdown(); - _removeCategoryDropdown(); - _removeSubCategoryDropdown(); - _removeSubSubCategoryDropdown(); - }, - child: FormLayoutTemplate( - title: _controller.isEditMode ? '์žฅ๋น„ ์ž…๊ณ  ์ˆ˜์ •' : '์žฅ๋น„ ์ž…๊ณ  ๋“ฑ๋ก', - onSave: _controller.isLoading ? null : _saveEquipmentIn, - onCancel: () => Navigator.of(context).pop(), - saveButtonText: _controller.isEditMode ? '์ˆ˜์ • ์™„๋ฃŒ' : '์ž…๊ณ  ๋“ฑ๋ก', - isLoading: _controller.isSaving, - child: Form( - key: _controller.formKey, - child: SingleChildScrollView( - padding: const EdgeInsets.all(UIConstants.formPadding), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + ), + ); + } + + Widget _buildBasicFields() { + return Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '๊ธฐ๋ณธ ์ •๋ณด', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + + // ์žฅ๋น„ ๋ฒˆํ˜ธ (ํ•„์ˆ˜) + TextFormField( + initialValue: _controller.equipmentNumber, + readOnly: _controller.isFieldReadOnly('equipmentNumber'), + decoration: InputDecoration( + labelText: _controller.isFieldReadOnly('equipmentNumber') + ? '์žฅ๋น„ ๋ฒˆํ˜ธ * ๐Ÿ”’' : '์žฅ๋น„ ๋ฒˆํ˜ธ *', + // ๐Ÿ”ง [UI FIX] ReadOnly ํ•„๋“œ์—์„œ๋„ ์˜๋ฏธ ์žˆ๋Š” hintText ํ‘œ์‹œ + hintText: _controller.isFieldReadOnly('equipmentNumber') + ? (_controller.equipmentNumber.isNotEmpty ? null : '์žฅ๋น„ ๋ฒˆํ˜ธ ์—†์Œ') + : '์žฅ๋น„ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”', + border: const OutlineInputBorder(), + filled: _controller.isFieldReadOnly('equipmentNumber'), + fillColor: _controller.isFieldReadOnly('equipmentNumber') + ? Colors.grey[100] : null, + ), + style: TextStyle( + color: _controller.isFieldReadOnly('equipmentNumber') + ? Colors.grey[600] : null, + ), + validator: (value) { + if (value?.trim().isEmpty ?? true) { + return '์žฅ๋น„ ๋ฒˆํ˜ธ๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค'; + } + return null; + }, + onChanged: _controller.isFieldReadOnly('equipmentNumber') ? null : (value) { + _controller.equipmentNumber = value?.trim() ?? ''; + setState(() {}); + print('DEBUG [์žฅ๋น„๋ฒˆํ˜ธ ์ž…๋ ฅ] value: "$value", controller.equipmentNumber: "${_controller.equipmentNumber}"'); + }, + onSaved: (value) { + _controller.equipmentNumber = value?.trim() ?? ''; + }, + ), + const SizedBox(height: 16), + + // ์ œ์กฐ์‚ฌ (ํ•„์ˆ˜, Dropdown) + DropdownButtonFormField( + value: _getValidManufacturer(), + items: _controller.manufacturers.map((String manufacturer) { + return DropdownMenuItem( + value: manufacturer, + child: Text( + manufacturer, + style: TextStyle( + color: _controller.isFieldReadOnly('manufacturer') + ? Colors.grey[600] : null, + ), + ), + ); + }).toList(), + decoration: InputDecoration( + labelText: _controller.isFieldReadOnly('manufacturer') + ? '์ œ์กฐ์‚ฌ * ๐Ÿ”’' : '์ œ์กฐ์‚ฌ *', + hintText: _controller.isFieldReadOnly('manufacturer') + ? '์ˆ˜์ •๋ถˆ๊ฐ€' : '์ œ์กฐ์‚ฌ๋ฅผ ์„ ํƒํ•˜์„ธ์š”', + border: const OutlineInputBorder(), + filled: _controller.isFieldReadOnly('manufacturer'), + fillColor: _controller.isFieldReadOnly('manufacturer') + ? Colors.grey[100] : null, + ), + validator: (value) { + if (value?.trim().isEmpty ?? true) { + return '์ œ์กฐ์‚ฌ๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค'; + } + return null; + }, + onChanged: _controller.isFieldReadOnly('manufacturer') ? null : (value) { + setState(() { + _controller.manufacturer = value?.trim() ?? ''; + }); + print('๐Ÿ”ง DEBUG [์ œ์กฐ์‚ฌ ์„ ํƒ] value: "$value", controller.manufacturer: "${_controller.manufacturer}", canSave: ${_controller.canSave}'); + }, + ), + const SizedBox(height: 16), + + // ๋ชจ๋ธ๋ช… (์„ ํƒ, Dropdown) + DropdownButtonFormField( + value: _getValidModelName(), + items: _controller.equipmentNames.map((String equipmentName) { + return DropdownMenuItem( + value: equipmentName, + child: Text( + equipmentName, + style: TextStyle( + color: _controller.isFieldReadOnly('modelName') + ? Colors.grey[600] : null, + ), + ), + ); + }).toList(), + decoration: InputDecoration( + labelText: _controller.isFieldReadOnly('modelName') + ? '๋ชจ๋ธ๋ช… ๐Ÿ”’' : '๋ชจ๋ธ๋ช…', + hintText: _controller.isFieldReadOnly('modelName') + ? '์ˆ˜์ •๋ถˆ๊ฐ€' : '๋ชจ๋ธ๋ช…์„ ์„ ํƒํ•˜์„ธ์š”', + border: const OutlineInputBorder(), + filled: _controller.isFieldReadOnly('modelName'), + fillColor: _controller.isFieldReadOnly('modelName') + ? Colors.grey[100] : null, + ), + onChanged: _controller.isFieldReadOnly('modelName') ? null : (value) { + setState(() { + _controller.modelName = value?.trim() ?? ''; + }); + print('DEBUG [๋ชจ๋ธ๋ช… ์„ ํƒ] value: "$value", controller.modelName: "${_controller.modelName}"'); + }, + ), + const SizedBox(height: 16), + + // ์‹œ๋ฆฌ์–ผ ๋ฒˆํ˜ธ (์„ ํƒ) + TextFormField( + initialValue: _controller.serialNumber, + readOnly: _controller.isFieldReadOnly('serialNumber'), + decoration: InputDecoration( + labelText: _controller.isFieldReadOnly('serialNumber') + ? '์‹œ๋ฆฌ์–ผ ๋ฒˆํ˜ธ ๐Ÿ”’' : '์‹œ๋ฆฌ์–ผ ๋ฒˆํ˜ธ', + hintText: _controller.isFieldReadOnly('serialNumber') + ? '์ˆ˜์ •๋ถˆ๊ฐ€' : '์‹œ๋ฆฌ์–ผ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”', + border: const OutlineInputBorder(), + filled: _controller.isFieldReadOnly('serialNumber'), + fillColor: _controller.isFieldReadOnly('serialNumber') + ? Colors.grey[100] : null, + ), + style: TextStyle( + color: _controller.isFieldReadOnly('serialNumber') + ? Colors.grey[600] : null, + ), + onChanged: _controller.isFieldReadOnly('serialNumber') ? null : (value) { + _controller.serialNumber = value?.trim() ?? ''; + setState(() {}); + print('DEBUG [์‹œ๋ฆฌ์–ผ๋ฒˆํ˜ธ ์ž…๋ ฅ] value: "$value", controller.serialNumber: "${_controller.serialNumber}"'); + }, + onSaved: (value) { + _controller.serialNumber = value?.trim() ?? ''; + }, + ), + ], + ), + ), + ); + } + + Widget _buildCategorySection() { + return Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '์žฅ๋น„ ๋ถ„๋ฅ˜', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + + CategoryCascadeFormField( + category1: _controller.category1.isEmpty ? null : _controller.category1, + category2: _controller.category2.isEmpty ? null : _controller.category2, + category3: _controller.category3.isEmpty ? null : _controller.category3, + onChanged: (cat1, cat2, cat3) { + _controller.category1 = cat1?.trim() ?? ''; + _controller.category2 = cat2?.trim() ?? ''; + _controller.category3 = cat3?.trim() ?? ''; + }, + ), + ], + ), + ), + ); + } + + Widget _buildLocationSection() { + return Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '์œ„์น˜ ์ •๋ณด', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + + // ๊ตฌ๋งค์ฒ˜ (๋“œ๋กญ๋‹ค์šด ์ „์šฉ) + DropdownButtonFormField( + value: _getValidCompanyId(), + items: _controller.companies.entries.map((entry) { + return DropdownMenuItem( + value: entry.key, + child: Text(entry.value), + ); + }).toList(), + decoration: const InputDecoration( + labelText: '๊ตฌ๋งค์ฒ˜', + hintText: '๊ตฌ๋งค์ฒ˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”', + border: OutlineInputBorder(), + ), + onChanged: (value) { + setState(() { + _controller.selectedCompanyId = value; + }); + print('DEBUG [๊ตฌ๋งค์ฒ˜ ์„ ํƒ] value: $value, companies: ${_controller.companies.length}'); + }, + onSaved: (value) { + _controller.selectedCompanyId = value; + }, + ), + const SizedBox(height: 16), + + // ์ž…๊ณ ์ง€ (๋“œ๋กญ๋‹ค์šด ์ „์šฉ) + DropdownButtonFormField( + value: _getValidWarehouseId(), + items: _controller.warehouses.entries.map((entry) { + return DropdownMenuItem( + value: entry.key, + child: Text(entry.value), + ); + }).toList(), + decoration: const InputDecoration( + labelText: '์ž…๊ณ ์ง€', + hintText: '์ž…๊ณ ์ง€๋ฅผ ์„ ํƒํ•˜์„ธ์š”', + border: OutlineInputBorder(), + ), + onChanged: (value) { + setState(() { + _controller.selectedWarehouseId = value; + }); + print('DEBUG [์ž…๊ณ ์ง€ ์„ ํƒ] value: $value, warehouses: ${_controller.warehouses.length}'); + }, + onSaved: (value) { + _controller.selectedWarehouseId = value; + }, + ), + ], + ), + ), + ); + } + + Widget _buildPurchaseSection() { + return Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '๊ตฌ๋งค ์ •๋ณด', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + + Row( children: [ - // ๊ธฐ๋ณธ ์ •๋ณด ์„น์…˜ - FormSection( - title: '๊ธฐ๋ณธ ์ •๋ณด', - subtitle: '์ž…๊ณ ํ•  ์žฅ๋น„์˜ ๊ธฐ๋ณธ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”', - children: [ - // ์žฅ๋น„ ์œ ํ˜• ์„ ํƒ (๋ผ๋””์˜ค ๋ฒ„ํŠผ) - FormFieldWrapper( - label: '์žฅ๋น„ ์œ ํ˜•', - required: true, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: RadioListTile( - title: const Text( - '์‹ ์ œํ’ˆ', - style: TextStyle(fontSize: 14), - ), - value: EquipmentType.new_, - groupValue: _controller.equipmentType, - onChanged: (value) { - setState(() { - _controller.equipmentType = value!; - }); - }, - contentPadding: EdgeInsets.zero, - dense: true, - ), - ), - Expanded( - child: RadioListTile( - title: const Text( - '์ค‘๊ณ ', - style: TextStyle(fontSize: 14), - ), - value: EquipmentType.used, - groupValue: _controller.equipmentType, - onChanged: (value) { - setState(() { - _controller.equipmentType = value!; - }); - }, - contentPadding: EdgeInsets.zero, - dense: true, - ), - ), - Expanded( - child: RadioListTile( - title: const Text( - '๊ณ„์•ฝ', - style: TextStyle(fontSize: 14), - ), - subtitle: const Text( - '(์ž…๊ณ ํ›„ ์ฆ‰๊ฐ ์ถœ๊ณ )', - style: TextStyle(fontSize: 11), - ), - value: EquipmentType.contract, - groupValue: _controller.equipmentType, - onChanged: (value) { - setState(() { - _controller.equipmentType = value!; - }); - }, - contentPadding: EdgeInsets.zero, - dense: true, - ), - ), - ], - ), - ], - ), - ), - // 1ํ–‰: ๊ตฌ๋งค์ฒ˜(ํŒŒํŠธ๋„ˆ์‚ฌ), ์ž…๊ณ ์ง€ - Row( - children: [ - Expanded( - child: FormFieldWrapper( - label: '๊ตฌ๋งค์ฒ˜', - required: true, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // ์ž…๋ ฅ๋ž€(CompositedTransformTarget์œผ๋กœ ๊ฐ์‹ธ๊ธฐ) - CompositedTransformTarget( - link: _partnerLayerLink, - child: TextFormField( - key: _partnerFieldKey, - controller: _partnerController, - focusNode: _partnerFocusNode, - decoration: InputDecoration( - labelText: '๊ตฌ๋งค์ฒ˜', - hintText: '๊ตฌ๋งค์ฒ˜๋ฅผ ์ž…๋ ฅ ๋˜๋Š” ์„ ํƒํ•˜์„ธ์š”', - suffixIcon: IconButton( - icon: const Icon(Icons.arrow_drop_down), - onPressed: _showPartnerDropdown, - ), - ), - onChanged: (value) { - print('[๊ตฌ๋งค์ฒ˜:onChanged] ์ž…๋ ฅ๊ฐ’: "$value"'); - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ์ด๋ฉด ๋ฌด์‹œ - if (_isProgrammaticPartnerChange) { - print('[๊ตฌ๋งค์ฒ˜:onChanged] ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ์ด๋ฏ€๋กœ ๋ฌด์‹œ'); - return; - } - setState(() { - print( - '[๊ตฌ๋งค์ฒ˜:setState:onChanged] _controller.partnerCompany <- "$value"', - ); - _controller.partnerCompany = value; - }); - }, - onFieldSubmitted: (value) { - // ์—”ํ„ฐ ์ž…๋ ฅ ์‹œ ์ž๋™์™„์„ฑ - print( - '[๊ตฌ๋งค์ฒ˜:onFieldSubmitted] ์—”ํ„ฐ ์ž…๋ ฅ๋จ, ์ž…๋ ฅ๊ฐ’: "$value", ์ž๋™์™„์„ฑ ํ›„๋ณด: "$suggestion", showSuggestion=$showSuggestion', - ); - if (showSuggestion) { - setState(() { - print( - '[๊ตฌ๋งค์ฒ˜:onFieldSubmitted] ์ž๋™์™„์„ฑ ์ ์šฉ: "$suggestion"', - ); - _isProgrammaticPartnerChange = true; - _partnerController.text = suggestion; - _controller.partnerCompany = suggestion; - // ์ปค์„œ๋ฅผ ๋งจ ๋’ค๋กœ ์ด๋™ - _partnerController - .selection = TextSelection.collapsed( - offset: suggestion.length, - ); - print( - '[๊ตฌ๋งค์ฒ˜:onFieldSubmitted] ์ปค์„œ ์œ„์น˜: ${_partnerController.selection.start}', - ); - }); - WidgetsBinding.instance - .addPostFrameCallback((_) { - _isProgrammaticPartnerChange = false; - }); - } - }, - ), - ), - // ์ž…๋ ฅ๋ž€ ์•„๋ž˜์— ์ž๋™์™„์„ฑ ํ›„๋ณด ์ „์ฒด๋ฅผ ๋” ์ž‘์€ ๊ธ€์”จ๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œ์‹œ - if (showSuggestion) - Padding( - padding: const EdgeInsets.only( - left: 12, - top: 2, - ), - child: Text( - suggestion, - style: const TextStyle( - color: Color(0xFF1976D2), - fontWeight: FontWeight.bold, - fontSize: 13, // ๋” ์ž‘์€ ๊ธ€์”จ - ), - ), - ), - ], - ), - ), - ), - const SizedBox(width: 16), - Expanded( - child: FormFieldWrapper( - label: '์ž…๊ณ ์ง€', - required: true, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // ์ž…๋ ฅ๋ž€(CompositedTransformTarget์œผ๋กœ ๊ฐ์‹ธ๊ธฐ) - CompositedTransformTarget( - link: _warehouseLayerLink, - child: TextFormField( - key: _warehouseFieldKey, - controller: _warehouseController, - focusNode: _warehouseFocusNode, - decoration: InputDecoration( - labelText: '์ž…๊ณ ์ง€', - hintText: '์ž…๊ณ ์ง€๋ฅผ ์ž…๋ ฅ ๋˜๋Š” ์„ ํƒํ•˜์„ธ์š”', - suffixIcon: IconButton( - icon: const Icon(Icons.arrow_drop_down), - onPressed: _showWarehouseDropdown, - ), - ), - onChanged: (value) { - print('[์ž…๊ณ ์ง€:onChanged] ์ž…๋ ฅ๊ฐ’: "$value"'); - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ์ด๋ฉด ๋ฌด์‹œ - if (_isProgrammaticWarehouseChange) { - print('[์ž…๊ณ ์ง€:onChanged] ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ์ด๋ฏ€๋กœ ๋ฌด์‹œ'); - return; - } - setState(() { - print( - '[์ž…๊ณ ์ง€:setState:onChanged] _controller.warehouseLocation <- "$value"', - ); - _controller.warehouseLocation = value; - }); - }, - onFieldSubmitted: (value) { - // ์—”ํ„ฐ ์ž…๋ ฅ ์‹œ ์ž๋™์™„์„ฑ - final warehouseSuggestion = - _getWarehouseAutocompleteSuggestion( - value, - ); - final showWarehouseSuggestion = - warehouseSuggestion != null && - warehouseSuggestion.length > value.length; - print( - '[์ž…๊ณ ์ง€:onFieldSubmitted] ์—”ํ„ฐ ์ž…๋ ฅ๋จ, ์ž…๋ ฅ๊ฐ’: "$value", ์ž๋™์™„์„ฑ ํ›„๋ณด: "$warehouseSuggestion", showWarehouseSuggestion=$showWarehouseSuggestion', - ); - if (showWarehouseSuggestion) { - setState(() { - print( - '[์ž…๊ณ ์ง€:onFieldSubmitted] ์ž๋™์™„์„ฑ ์ ์šฉ: "$warehouseSuggestion"', - ); - _isProgrammaticWarehouseChange = true; - _warehouseController.text = - warehouseSuggestion; - _controller.warehouseLocation = - warehouseSuggestion; - // ์ปค์„œ๋ฅผ ๋งจ ๋’ค๋กœ ์ด๋™ - _warehouseController - .selection = TextSelection.collapsed( - offset: warehouseSuggestion.length, - ); - print( - '[์ž…๊ณ ์ง€:onFieldSubmitted] ์ปค์„œ ์œ„์น˜: ${_warehouseController.selection.start}', - ); - }); - WidgetsBinding.instance - .addPostFrameCallback((_) { - _isProgrammaticWarehouseChange = - false; - }); - } - }, - ), - ), - // ์ž…๋ ฅ๋ž€ ์•„๋ž˜์— ์ž๋™์™„์„ฑ ํ›„๋ณด ์ „์ฒด๋ฅผ ๋” ์ž‘์€ ๊ธ€์”จ๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œ์‹œ - if (_getWarehouseAutocompleteSuggestion( - _warehouseController.text, - ) != - null && - _getWarehouseAutocompleteSuggestion( - _warehouseController.text, - )!.length > - _warehouseController.text.length) - Padding( - padding: const EdgeInsets.only( - left: 12, - top: 2, - ), - child: Text( - _getWarehouseAutocompleteSuggestion( - _warehouseController.text, - )!, - style: const TextStyle( - color: Color(0xFF1976D2), - fontWeight: FontWeight.bold, - fontSize: 13, // ๋” ์ž‘์€ ๊ธ€์”จ - ), - ), - ), - ], - ), - ), - ), - ], - ), - // 2ํ–‰: ์ œ์กฐ์‚ฌ, ์žฅ๋น„๋ช… - Row( - children: [ - Expanded( - child: FormFieldWrapper( - label: '์ œ์กฐ์‚ฌ', - required: true, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // ์ž…๋ ฅ๋ž€(CompositedTransformTarget์œผ๋กœ ๊ฐ์‹ธ๊ธฐ) - CompositedTransformTarget( - link: _manufacturerLayerLink, - child: TextFormField( - key: _manufacturerFieldKey, - controller: _manufacturerController, - focusNode: _manufacturerFocusNode, - decoration: InputDecoration( - labelText: '์ œ์กฐ์‚ฌ', - hintText: '์ œ์กฐ์‚ฌ๋ฅผ ์ž…๋ ฅ ๋˜๋Š” ์„ ํƒํ•˜์„ธ์š”', - suffixIcon: IconButton( - icon: const Icon(Icons.arrow_drop_down), - onPressed: _showManufacturerDropdown, - ), - ), - onChanged: (value) { - print('[์ œ์กฐ์‚ฌ:onChanged] ์ž…๋ ฅ๊ฐ’: "$value"'); - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ์ด๋ฉด ๋ฌด์‹œ - if (_isProgrammaticManufacturerChange) { - print('[์ œ์กฐ์‚ฌ:onChanged] ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ์ด๋ฏ€๋กœ ๋ฌด์‹œ'); - return; - } - setState(() { - print( - '[์ œ์กฐ์‚ฌ:setState:onChanged] _controller.manufacturer <- "$value"', - ); - _controller.manufacturer = value; - }); - }, - onFieldSubmitted: (value) { - // ์—”ํ„ฐ ์ž…๋ ฅ ์‹œ ์ž๋™์™„์„ฑ - final manufacturerSuggestion = - _getManufacturerAutocompleteSuggestion( - value, - ); - final showManufacturerSuggestion = - manufacturerSuggestion != null && - manufacturerSuggestion.length > - value.length; - print( - '[์ œ์กฐ์‚ฌ:onFieldSubmitted] ์—”ํ„ฐ ์ž…๋ ฅ๋จ, ์ž…๋ ฅ๊ฐ’: "$value", ์ž๋™์™„์„ฑ ํ›„๋ณด: "$manufacturerSuggestion", showManufacturerSuggestion=$showManufacturerSuggestion', - ); - if (showManufacturerSuggestion) { - setState(() { - print( - '[์ œ์กฐ์‚ฌ:onFieldSubmitted] ์ž๋™์™„์„ฑ ์ ์šฉ: "$manufacturerSuggestion"', - ); - _isProgrammaticManufacturerChange = true; - _manufacturerController.text = - manufacturerSuggestion; - _controller.manufacturer = - manufacturerSuggestion; - // ์ปค์„œ๋ฅผ ๋งจ ๋’ค๋กœ ์ด๋™ - _manufacturerController - .selection = TextSelection.collapsed( - offset: manufacturerSuggestion.length, - ); - print( - '[์ œ์กฐ์‚ฌ:onFieldSubmitted] ์ปค์„œ ์œ„์น˜: ${_manufacturerController.selection.start}', - ); - }); - WidgetsBinding.instance - .addPostFrameCallback((_) { - _isProgrammaticManufacturerChange = - false; - }); - } - }, - ), - ), - // ์ž…๋ ฅ๋ž€ ์•„๋ž˜์— ์ž๋™์™„์„ฑ ํ›„๋ณด ์ „์ฒด๋ฅผ ๋” ์ž‘์€ ๊ธ€์”จ๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œ์‹œ - if (_getManufacturerAutocompleteSuggestion( - _manufacturerController.text, - ) != - null && - _getManufacturerAutocompleteSuggestion( - _manufacturerController.text, - )!.length > - _manufacturerController.text.length) - Padding( - padding: const EdgeInsets.only( - left: 12, - top: 2, - ), - child: Text( - _getManufacturerAutocompleteSuggestion( - _manufacturerController.text, - )!, - style: const TextStyle( - color: Color(0xFF1976D2), - fontWeight: FontWeight.bold, - fontSize: 13, // ๋” ์ž‘์€ ๊ธ€์”จ - ), - ), - ), - ], - ), - ), - ), - const SizedBox(width: 16), - Expanded( - child: FormFieldWrapper( - label: '์žฅ๋น„๋ช…', - required: true, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // ์ž…๋ ฅ๋ž€(CompositedTransformTarget์œผ๋กœ ๊ฐ์‹ธ๊ธฐ) - CompositedTransformTarget( - link: _equipmentNameLayerLink, - child: TextFormField( - key: _equipmentNameFieldKey, - controller: _equipmentNameController, - focusNode: _nameFieldFocusNode, - decoration: InputDecoration( - labelText: '์žฅ๋น„๋ช…', - hintText: '์žฅ๋น„๋ช…์„ ์ž…๋ ฅ ๋˜๋Š” ์„ ํƒํ•˜์„ธ์š”', - suffixIcon: IconButton( - icon: const Icon(Icons.arrow_drop_down), - onPressed: _showEquipmentNameDropdown, - ), - ), - onChanged: (value) { - print('[์žฅ๋น„๋ช…:onChanged] ์ž…๋ ฅ๊ฐ’: "$value"'); - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ์ด๋ฉด ๋ฌด์‹œ - if (_isProgrammaticEquipmentNameChange) { - print('[์žฅ๋น„๋ช…:onChanged] ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ์ด๋ฏ€๋กœ ๋ฌด์‹œ'); - return; - } - setState(() { - print( - '[์žฅ๋น„๋ช…:setState:onChanged] _controller.name <- "$value"', - ); - _controller.name = value; - }); - }, - onFieldSubmitted: (value) { - // ์—”ํ„ฐ ์ž…๋ ฅ ์‹œ ์ž๋™์™„์„ฑ - final equipmentNameSuggestion = - _getEquipmentNameAutocompleteSuggestion( - value, - ); - final showEquipmentNameSuggestion = - equipmentNameSuggestion != null && - equipmentNameSuggestion.length > - value.length; - print( - '[์žฅ๋น„๋ช…:onFieldSubmitted] ์—”ํ„ฐ ์ž…๋ ฅ๋จ, ์ž…๋ ฅ๊ฐ’: "$value", ์ž๋™์™„์„ฑ ํ›„๋ณด: "$equipmentNameSuggestion", showEquipmentNameSuggestion=$showEquipmentNameSuggestion', - ); - if (showEquipmentNameSuggestion) { - setState(() { - print( - '[์žฅ๋น„๋ช…:onFieldSubmitted] ์ž๋™์™„์„ฑ ์ ์šฉ: "$equipmentNameSuggestion"', - ); - _isProgrammaticEquipmentNameChange = true; - _equipmentNameController.text = - equipmentNameSuggestion; - _controller.name = - equipmentNameSuggestion; - // ์ปค์„œ๋ฅผ ๋งจ ๋’ค๋กœ ์ด๋™ - _equipmentNameController - .selection = TextSelection.collapsed( - offset: equipmentNameSuggestion.length, - ); - print( - '[์žฅ๋น„๋ช…:onFieldSubmitted] ์ปค์„œ ์œ„์น˜: ${_equipmentNameController.selection.start}', - ); - }); - WidgetsBinding.instance - .addPostFrameCallback((_) { - _isProgrammaticEquipmentNameChange = - false; - }); - } - }, - ), - ), - // ์ž…๋ ฅ๋ž€ ์•„๋ž˜์— ์ž๋™์™„์„ฑ ํ›„๋ณด ์ „์ฒด๋ฅผ ๋” ์ž‘์€ ๊ธ€์”จ๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œ์‹œ - if (_getEquipmentNameAutocompleteSuggestion( - _equipmentNameController.text, - ) != - null && - _getEquipmentNameAutocompleteSuggestion( - _equipmentNameController.text, - )!.length > - _equipmentNameController.text.length) - Padding( - padding: const EdgeInsets.only( - left: 12, - top: 2, - ), - child: Text( - _getEquipmentNameAutocompleteSuggestion( - _equipmentNameController.text, - )!, - style: const TextStyle( - color: Color(0xFF1976D2), - fontWeight: FontWeight.bold, - fontSize: 13, // ๋” ์ž‘์€ ๊ธ€์”จ - ), - ), - ), - ], - ), - ), - ), - ], - ), - // 3ํ–‰: ๋Œ€๋ถ„๋ฅ˜, ์ค‘๋ถ„๋ฅ˜, ์†Œ๋ถ„๋ฅ˜ - Row( - children: [ - Expanded( - child: FormFieldWrapper( - label: '๋Œ€๋ถ„๋ฅ˜', - required: true, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // ์ž…๋ ฅ๋ž€(CompositedTransformTarget์œผ๋กœ ๊ฐ์‹ธ๊ธฐ) - CompositedTransformTarget( - link: _categoryLayerLink, - child: TextFormField( - key: _categoryFieldKey, - controller: _categoryController, - focusNode: _categoryFocusNode, - decoration: InputDecoration( - labelText: '๋Œ€๋ถ„๋ฅ˜', - hintText: '๋Œ€๋ถ„๋ฅ˜๋ฅผ ์ž…๋ ฅ ๋˜๋Š” ์„ ํƒํ•˜์„ธ์š”', - suffixIcon: IconButton( - icon: const Icon(Icons.arrow_drop_down), - onPressed: _showCategoryDropdown, - ), - ), - onChanged: (value) { - print('[๋Œ€๋ถ„๋ฅ˜:onChanged] ์ž…๋ ฅ๊ฐ’: "$value"'); - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ์ด๋ฉด ๋ฌด์‹œ - if (_isProgrammaticCategoryChange) { - print('[๋Œ€๋ถ„๋ฅ˜:onChanged] ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ์ด๋ฏ€๋กœ ๋ฌด์‹œ'); - return; - } - setState(() { - print( - '[๋Œ€๋ถ„๋ฅ˜:setState:onChanged] _controller.category <- "$value"', - ); - _controller.category = value; - }); - }, - onFieldSubmitted: (value) { - // ์—”ํ„ฐ ์ž…๋ ฅ ์‹œ ์ž๋™์™„์„ฑ - final categorySuggestion = - _getCategoryAutocompleteSuggestion(value); - final showCategorySuggestion = - categorySuggestion != null && - categorySuggestion.length > value.length; - print( - '[๋Œ€๋ถ„๋ฅ˜:onFieldSubmitted] ์—”ํ„ฐ ์ž…๋ ฅ๋จ, ์ž…๋ ฅ๊ฐ’: "$value", ์ž๋™์™„์„ฑ ํ›„๋ณด: "$categorySuggestion", showCategorySuggestion=$showCategorySuggestion', - ); - if (showCategorySuggestion) { - setState(() { - print( - '[๋Œ€๋ถ„๋ฅ˜:onFieldSubmitted] ์ž๋™์™„์„ฑ ์ ์šฉ: "$categorySuggestion"', - ); - _isProgrammaticCategoryChange = true; - _categoryController.text = - categorySuggestion; - _controller.category = categorySuggestion; - // ์ปค์„œ๋ฅผ ๋งจ ๋’ค๋กœ ์ด๋™ - _categoryController - .selection = TextSelection.collapsed( - offset: categorySuggestion.length, - ); - print( - '[๋Œ€๋ถ„๋ฅ˜:onFieldSubmitted] ์ปค์„œ ์œ„์น˜: ${_categoryController.selection.start}', - ); - }); - WidgetsBinding.instance - .addPostFrameCallback((_) { - _isProgrammaticCategoryChange = false; - }); - } - }, - ), - ), - // ์ž…๋ ฅ๋ž€ ์•„๋ž˜์— ์ž๋™์™„์„ฑ ํ›„๋ณด ์ „์ฒด๋ฅผ ๋” ์ž‘์€ ๊ธ€์”จ๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œ์‹œ - if (_getCategoryAutocompleteSuggestion( - _categoryController.text, - ) != - null && - _getCategoryAutocompleteSuggestion( - _categoryController.text, - )!.length > - _categoryController.text.length) - Padding( - padding: const EdgeInsets.only( - left: 12, - top: 2, - ), - child: Text( - _getCategoryAutocompleteSuggestion( - _categoryController.text, - )!, - style: const TextStyle( - color: Color(0xFF1976D2), - fontWeight: FontWeight.bold, - fontSize: 13, // ๋” ์ž‘์€ ๊ธ€์”จ - ), - ), - ), - ], - ), - ), - ), - const SizedBox(width: 16), - Expanded( - child: FormFieldWrapper( - label: '์ค‘๋ถ„๋ฅ˜', - required: true, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // ์ž…๋ ฅ๋ž€(CompositedTransformTarget์œผ๋กœ ๊ฐ์‹ธ๊ธฐ) - CompositedTransformTarget( - link: _subCategoryLayerLink, - child: TextFormField( - key: _subCategoryFieldKey, - controller: _subCategoryController, - focusNode: _subCategoryFocusNode, - decoration: InputDecoration( - labelText: '์ค‘๋ถ„๋ฅ˜', - hintText: '์ค‘๋ถ„๋ฅ˜๋ฅผ ์ž…๋ ฅ ๋˜๋Š” ์„ ํƒํ•˜์„ธ์š”', - suffixIcon: IconButton( - icon: const Icon(Icons.arrow_drop_down), - onPressed: _showSubCategoryDropdown, - ), - ), - onChanged: (value) { - print('[์ค‘๋ถ„๋ฅ˜:onChanged] ์ž…๋ ฅ๊ฐ’: "$value"'); - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ์ด๋ฉด ๋ฌด์‹œ - if (_isProgrammaticSubCategoryChange) { - print('[์ค‘๋ถ„๋ฅ˜:onChanged] ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ์ด๋ฏ€๋กœ ๋ฌด์‹œ'); - return; - } - setState(() { - print( - '[์ค‘๋ถ„๋ฅ˜:setState:onChanged] _controller.subCategory <- "$value"', - ); - _controller.subCategory = value; - }); - }, - onFieldSubmitted: (value) { - // ์—”ํ„ฐ ์ž…๋ ฅ ์‹œ ์ž๋™์™„์„ฑ - final subCategorySuggestion = - _getSubCategoryAutocompleteSuggestion( - value, - ); - final showSubCategorySuggestion = - subCategorySuggestion != null && - subCategorySuggestion.length > - value.length; - print( - '[์ค‘๋ถ„๋ฅ˜:onFieldSubmitted] ์—”ํ„ฐ ์ž…๋ ฅ๋จ, ์ž…๋ ฅ๊ฐ’: "$value", ์ž๋™์™„์„ฑ ํ›„๋ณด: "$subCategorySuggestion", showSubCategorySuggestion=$showSubCategorySuggestion', - ); - if (showSubCategorySuggestion) { - setState(() { - print( - '[์ค‘๋ถ„๋ฅ˜:onFieldSubmitted] ์ž๋™์™„์„ฑ ์ ์šฉ: "$subCategorySuggestion"', - ); - _isProgrammaticSubCategoryChange = true; - _subCategoryController.text = - subCategorySuggestion; - _controller.subCategory = - subCategorySuggestion; - // ์ปค์„œ๋ฅผ ๋งจ ๋’ค๋กœ ์ด๋™ - _subCategoryController - .selection = TextSelection.collapsed( - offset: subCategorySuggestion.length, - ); - print( - '[์ค‘๋ถ„๋ฅ˜:onFieldSubmitted] ์ปค์„œ ์œ„์น˜: ${_subCategoryController.selection.start}', - ); - }); - WidgetsBinding.instance - .addPostFrameCallback((_) { - _isProgrammaticSubCategoryChange = - false; - }); - } - }, - ), - ), - // ์ž…๋ ฅ๋ž€ ์•„๋ž˜์— ์ž๋™์™„์„ฑ ํ›„๋ณด ์ „์ฒด๋ฅผ ๋” ์ž‘์€ ๊ธ€์”จ๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œ์‹œ - if (_getSubCategoryAutocompleteSuggestion( - _subCategoryController.text, - ) != - null && - _getSubCategoryAutocompleteSuggestion( - _subCategoryController.text, - )!.length > - _subCategoryController.text.length) - Padding( - padding: const EdgeInsets.only( - left: 12, - top: 2, - ), - child: Text( - _getSubCategoryAutocompleteSuggestion( - _subCategoryController.text, - )!, - style: const TextStyle( - color: Color(0xFF1976D2), - fontWeight: FontWeight.bold, - fontSize: 13, // ๋” ์ž‘์€ ๊ธ€์”จ - ), - ), - ), - ], - ), - ), - ), - const SizedBox(width: 16), - Expanded( - child: FormFieldWrapper( - label: '์†Œ๋ถ„๋ฅ˜', - required: true, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // ์ž…๋ ฅ๋ž€(CompositedTransformTarget์œผ๋กœ ๊ฐ์‹ธ๊ธฐ) - CompositedTransformTarget( - link: _subSubCategoryLayerLink, - child: TextFormField( - key: _subSubCategoryFieldKey, - controller: _subSubCategoryController, - focusNode: _subSubCategoryFocusNode, - decoration: InputDecoration( - labelText: '์†Œ๋ถ„๋ฅ˜', - hintText: '์†Œ๋ถ„๋ฅ˜๋ฅผ ์ž…๋ ฅ ๋˜๋Š” ์„ ํƒํ•˜์„ธ์š”', - suffixIcon: IconButton( - icon: const Icon(Icons.arrow_drop_down), - onPressed: _showSubSubCategoryDropdown, - ), - ), - onChanged: (value) { - print('[์†Œ๋ถ„๋ฅ˜:onChanged] ์ž…๋ ฅ๊ฐ’: "$value"'); - // ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ์ด๋ฉด ๋ฌด์‹œ - if (_isProgrammaticSubSubCategoryChange) { - print('[์†Œ๋ถ„๋ฅ˜:onChanged] ํ”„๋กœ๊ทธ๋žจ์  ๋ณ€๊ฒฝ์ด๋ฏ€๋กœ ๋ฌด์‹œ'); - return; - } - setState(() { - print( - '[์†Œ๋ถ„๋ฅ˜:setState:onChanged] _controller.subSubCategory <- "$value"', - ); - _controller.subSubCategory = value; - }); - }, - onFieldSubmitted: (value) { - // ์—”ํ„ฐ ์ž…๋ ฅ ์‹œ ์ž๋™์™„์„ฑ - final subSubCategorySuggestion = - _getSubSubCategoryAutocompleteSuggestion( - value, - ); - final showSubSubCategorySuggestion = - subSubCategorySuggestion != null && - subSubCategorySuggestion.length > - value.length; - print( - '[์†Œ๋ถ„๋ฅ˜:onFieldSubmitted] ์—”ํ„ฐ ์ž…๋ ฅ๋จ, ์ž…๋ ฅ๊ฐ’: "$value", ์ž๋™์™„์„ฑ ํ›„๋ณด: "$subSubCategorySuggestion", showSubSubCategorySuggestion=$showSubSubCategorySuggestion', - ); - if (showSubSubCategorySuggestion) { - setState(() { - print( - '[์†Œ๋ถ„๋ฅ˜:onFieldSubmitted] ์ž๋™์™„์„ฑ ์ ์šฉ: "$subSubCategorySuggestion"', - ); - _isProgrammaticSubSubCategoryChange = - true; - _subSubCategoryController.text = - subSubCategorySuggestion; - _controller.subSubCategory = - subSubCategorySuggestion; - // ์ปค์„œ๋ฅผ ๋งจ ๋’ค๋กœ ์ด๋™ - _subSubCategoryController - .selection = TextSelection.collapsed( - offset: subSubCategorySuggestion.length, - ); - print( - '[์†Œ๋ถ„๋ฅ˜:onFieldSubmitted] ์ปค์„œ ์œ„์น˜: ${_subSubCategoryController.selection.start}', - ); - }); - WidgetsBinding.instance - .addPostFrameCallback((_) { - _isProgrammaticSubSubCategoryChange = - false; - }); - } - }, - ), - ), - // ์ž…๋ ฅ๋ž€ ์•„๋ž˜์— ์ž๋™์™„์„ฑ ํ›„๋ณด ์ „์ฒด๋ฅผ ๋” ์ž‘์€ ๊ธ€์”จ๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œ์‹œ - if (_getSubSubCategoryAutocompleteSuggestion( - _subSubCategoryController.text, - ) != - null && - _getSubSubCategoryAutocompleteSuggestion( - _subSubCategoryController.text, - )!.length > - _subSubCategoryController.text.length) - Padding( - padding: const EdgeInsets.only( - left: 12, - top: 2, - ), - child: Text( - _getSubSubCategoryAutocompleteSuggestion( - _subSubCategoryController.text, - )!, - style: const TextStyle( - color: Color(0xFF1976D2), - fontWeight: FontWeight.bold, - fontSize: 13, // ๋” ์ž‘์€ ๊ธ€์”จ - ), - ), - ), - ], - ), - ), - ), - ], - ), - // ์‹œ๋ฆฌ์–ผ ๋ฒˆํ˜ธ ์œ ๋ฌด ํ† ๊ธ€ - FormFieldWrapper( - label: '์‹œ๋ฆฌ์–ผ ๋ฒˆํ˜ธ', - required: false, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Checkbox( - value: _controller.hasSerialNumber, - onChanged: (value) { - setState(() { - _controller.hasSerialNumber = value ?? true; - }); - }, - ), - const Text('์‹œ๋ฆฌ์–ผ ๋ฒˆํ˜ธ ์žˆ์Œ'), - ], - ), - if (_controller.hasSerialNumber) - TextFormField( - initialValue: _controller.serialNumber, - decoration: const InputDecoration( - hintText: '์‹œ๋ฆฌ์–ผ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”', - ), - validator: (value) { - if (_controller.hasSerialNumber && - (value == null || value.isEmpty)) { - return '์‹œ๋ฆฌ์–ผ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”'; - } - return null; - }, - onSaved: (value) { - _controller.serialNumber = value ?? ''; - }, - ), - ], - ), - ), - // ๋ฐ”์ฝ”๋“œ ํ•„๋“œ - FormFieldWrapper( - label: '๋ฐ”์ฝ”๋“œ', - required: false, - child: TextFormField( - initialValue: _controller.barcode, - decoration: const InputDecoration( - hintText: '๋ฐ”์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š” (์„ ํƒ์‚ฌํ•ญ)', - ), - onSaved: (value) { - _controller.barcode = value ?? ''; - }, - ), - ), - // ์ˆ˜๋Ÿ‰ ํ•„๋“œ - FormFieldWrapper( - label: '์ˆ˜๋Ÿ‰', - required: true, - child: TextFormField( - initialValue: _controller.quantity.toString(), - decoration: const InputDecoration(hintText: '์ˆ˜๋Ÿ‰์„ ์ž…๋ ฅํ•˜์„ธ์š”'), - keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - validator: (value) { - if (value == null || value.isEmpty) { - return '์ˆ˜๋Ÿ‰์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”'; - } - if (int.tryParse(value) == null || - int.parse(value) <= 0) { - return '์œ ํšจํ•œ ์ˆ˜๋Ÿ‰์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”'; - } - return null; - }, - onSaved: (value) { - _controller.quantity = int.tryParse(value ?? '1') ?? 1; - }, - ), - ), - // ์ž…๊ณ ์ผ ํ•„๋“œ - FormFieldWrapper( - label: '์ž…๊ณ ์ผ', - required: true, + // ๊ตฌ๋งค์ผ + Expanded( child: InkWell( - onTap: () async { - final DateTime? picked = await showDatePicker( + onTap: _controller.isFieldReadOnly('purchaseDate') ? null : () async { + final date = await showDatePicker( context: context, - initialDate: _controller.inDate, + initialDate: _controller.purchaseDate ?? DateTime.now(), firstDate: DateTime(2000), - lastDate: DateTime.now(), + lastDate: DateTime(2100), ); - if (picked != null && picked != _controller.inDate) { + if (date != null) { setState(() { - _controller.inDate = picked; - // ์ž…๊ณ ์ผ ๋ณ€๊ฒฝ ์‹œ ์›Œ๋Ÿฐํ‹ฐ ์‹œ์ž‘์ผ๋„ ๊ฐ™์ด ๋ณ€๊ฒฝ - _controller.warrantyStartDate = picked; + _controller.purchaseDate = date; }); } }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 15, + child: InputDecorator( + decoration: InputDecoration( + labelText: _controller.isFieldReadOnly('purchaseDate') + ? '๊ตฌ๋งค์ผ ๐Ÿ”’' : '๊ตฌ๋งค์ผ', + suffixIcon: Icon( + Icons.calendar_today, + color: _controller.isFieldReadOnly('purchaseDate') + ? Colors.grey[600] : null, + ), + border: const OutlineInputBorder(), + filled: _controller.isFieldReadOnly('purchaseDate'), + fillColor: _controller.isFieldReadOnly('purchaseDate') + ? Colors.grey[100] : null, ), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade400), - borderRadius: BorderRadius.circular(4), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '${_controller.inDate.year}-${_controller.inDate.month.toString().padLeft(2, '0')}-${_controller.inDate.day.toString().padLeft(2, '0')}', - style: ShadcnTheme.bodyMedium, - ), - const Icon(Icons.calendar_today, size: 20), - ], + child: Text( + // ๐Ÿ”ง [UI FIX] ReadOnly ํ•„๋“œ์—์„œ๋„ ์˜๋ฏธ ์žˆ๋Š” ํ…์ŠคํŠธ ํ‘œ์‹œ + _controller.purchaseDate != null + ? '${_controller.purchaseDate!.year}-${_controller.purchaseDate!.month.toString().padLeft(2, '0')}-${_controller.purchaseDate!.day.toString().padLeft(2, '0')}' + : _controller.isFieldReadOnly('purchaseDate') ? '๊ตฌ๋งค์ผ ๋ฏธ์„ค์ •' : '๋‚ ์งœ ์„ ํƒ', + style: TextStyle( + color: _controller.isFieldReadOnly('purchaseDate') + ? Colors.grey[600] : null, + ), ), ), ), ), - - // ์›Œ๋Ÿฐํ‹ฐ ์ •๋ณด ์„น์…˜ - const SizedBox(height: 16), - Text('์›Œ๋Ÿฐํ‹ฐ ์ •๋ณด', style: Theme.of(context).textTheme.titleMedium), - const SizedBox(height: 12), - - // ์›Œ๋Ÿฐํ‹ฐ ํ•„๋“œ๋“ค์„ 1ํ–‰์œผ๋กœ ํ†ตํ•ฉ (์ „์ฒด ๋„ˆ๋น„ ์‚ฌ์šฉ) - SizedBox( - width: double.infinity, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // ์›Œ๋Ÿฐํ‹ฐ ๋ผ์ด์„ผ์Šค - Expanded( - flex: 2, - child: FormFieldWrapper( - label: '์›Œ๋Ÿฐํ‹ฐ ๋ผ์ด์„ผ์Šค', - required: false, - child: TextFormField( - initialValue: _controller.warrantyLicense ?? '', - decoration: const InputDecoration( - hintText: '์›Œ๋Ÿฐํ‹ฐ ๋ผ์ด์„ผ์Šค๋ช…์„ ์ž…๋ ฅํ•˜์„ธ์š”', - ), - onChanged: (value) { - _controller.warrantyLicense = value; - }, - ), - ), - ), - const SizedBox(width: 12), - - // ์›Œ๋Ÿฐํ‹ฐ ์ฝ”๋“œ ์ž…๋ ฅ๋ž€ ์ถ”๊ฐ€ - Expanded( - flex: 2, - child: FormFieldWrapper( - label: '์›Œ๋Ÿฐํ‹ฐ ์ฝ”๋“œ', - required: false, - child: TextFormField( - initialValue: _controller.warrantyCode ?? '', - decoration: const InputDecoration( - hintText: '์›Œ๋Ÿฐํ‹ฐ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”', - ), - onChanged: (value) { - _controller.warrantyCode = value; - }, - ), - ), - ), - const SizedBox(width: 12), - - // ์›Œ๋Ÿฐํ‹ฐ ์‹œ์ž‘์ผ - Expanded( - flex: 1, - child: FormFieldWrapper( - label: '์‹œ์ž‘์ผ', - required: false, - child: InkWell( - onTap: () async { - final DateTime? picked = await showDatePicker( - context: context, - initialDate: _controller.warrantyStartDate, - firstDate: DateTime(2000), - lastDate: DateTime(2100), - ); - if (picked != null && - picked != _controller.warrantyStartDate) { - setState(() { - _controller.warrantyStartDate = picked; - }); - } - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 15, - ), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade400), - borderRadius: BorderRadius.circular(4), - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - '${_controller.warrantyStartDate.year}-${_controller.warrantyStartDate.month.toString().padLeft(2, '0')}-${_controller.warrantyStartDate.day.toString().padLeft(2, '0')}', - style: ShadcnTheme.bodyMedium, - overflow: TextOverflow.ellipsis, - ), - ), - const Icon(Icons.calendar_today, size: 16), - ], - ), - ), - ), - ), - ), - const SizedBox(width: 12), - - // ์›Œ๋Ÿฐํ‹ฐ ์ข…๋ฃŒ์ผ - Expanded( - flex: 1, - child: FormFieldWrapper( - label: '์ข…๋ฃŒ์ผ', - required: false, - child: InkWell( - onTap: () async { - final DateTime? picked = await showDatePicker( - context: context, - initialDate: _controller.warrantyEndDate, - firstDate: DateTime(2000), - lastDate: DateTime(2100), - ); - if (picked != null && - picked != _controller.warrantyEndDate) { - setState(() { - _controller.warrantyEndDate = picked; - }); - } - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 15, - ), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade400), - borderRadius: BorderRadius.circular(4), - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - '${_controller.warrantyEndDate.year}-${_controller.warrantyEndDate.month.toString().padLeft(2, '0')}-${_controller.warrantyEndDate.day.toString().padLeft(2, '0')}', - style: ShadcnTheme.bodyMedium, - overflow: TextOverflow.ellipsis, - ), - ), - const Icon(Icons.calendar_today, size: 16), - ], - ), - ), - ), - ), - ), - const SizedBox(width: 12), - - // ์›Œ๋Ÿฐํ‹ฐ ๊ธฐ๊ฐ„ ์š”์•ฝ - Expanded( - flex: 1, - child: FormFieldWrapper( - label: '์›Œ๋Ÿฐํ‹ฐ ๊ธฐ๊ฐ„', - required: false, - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 15, - ), - decoration: BoxDecoration( - color: Colors.grey.shade100, - border: Border.all(color: Colors.grey.shade400), - borderRadius: BorderRadius.circular(4), - ), - alignment: Alignment.centerLeft, - child: Text( - ' ${_controller.getWarrantyPeriodSummary()}', - style: TextStyle( - color: Colors.grey.shade700, - fontWeight: FontWeight.bold, - ), - overflow: TextOverflow.ellipsis, - ), - ), - ), - ), - ], - ), - ), - - // ํ˜„์žฌ ์œ„์น˜ ๋ฐ ์ƒํƒœ ์ •๋ณด ์„น์…˜ - const SizedBox(height: 24), + const SizedBox(width: 16), - // ํ˜„์žฌ ํšŒ์‚ฌ ๋ฐ ์ง€์  ์ •๋ณด - Row( - children: [ - Expanded( - child: FormFieldWrapper( - label: 'ํ˜„์žฌ ํšŒ์‚ฌ', - required: false, - child: DropdownButtonFormField( - value: _controller.currentCompanyId?.toString(), - decoration: const InputDecoration( - hintText: 'ํ˜„์žฌ ๋ฐฐ์น˜๋œ ํšŒ์‚ฌ๋ฅผ ์„ ํƒํ•˜์„ธ์š”', - ), - items: const [ - DropdownMenuItem(value: null, child: Text('์„ ํƒํ•˜์ง€ ์•Š์Œ')), - // TODO: ์‹ค์ œ ํšŒ์‚ฌ ๋ชฉ๋ก์œผ๋กœ ๋Œ€์ฒด ํ•„์š” - ], - onChanged: (value) { - setState(() { - _controller.currentCompanyId = value != null ? int.tryParse(value) : null; - }); - }, - ), - ), + // ๊ตฌ๋งค ๊ฐ€๊ฒฉ + Expanded( + child: TextFormField( + initialValue: _controller.purchasePrice != null + ? CurrencyFormatter.formatKRW(_controller.purchasePrice) + : '', + readOnly: _controller.isFieldReadOnly('purchasePrice'), + decoration: InputDecoration( + labelText: _controller.isFieldReadOnly('purchasePrice') + ? '๊ตฌ๋งค ๊ฐ€๊ฒฉ ๐Ÿ”’' : '๊ตฌ๋งค ๊ฐ€๊ฒฉ', + hintText: _controller.isFieldReadOnly('purchasePrice') + ? '์ˆ˜์ •๋ถˆ๊ฐ€' : 'โ‚ฉ2,000,000', + border: const OutlineInputBorder(), + filled: _controller.isFieldReadOnly('purchasePrice'), + fillColor: _controller.isFieldReadOnly('purchasePrice') + ? Colors.grey[100] : null, ), - const SizedBox(width: 12), - Expanded( - child: FormFieldWrapper( - label: 'ํ˜„์žฌ ์ง€์ ', - required: false, - child: DropdownButtonFormField( - value: _controller.currentBranchId?.toString(), - decoration: const InputDecoration( - hintText: 'ํ˜„์žฌ ๋ฐฐ์น˜๋œ ์ง€์ ์„ ์„ ํƒํ•˜์„ธ์š”', - ), - items: const [ - DropdownMenuItem(value: null, child: Text('์„ ํƒํ•˜์ง€ ์•Š์Œ')), - // TODO: ์‹ค์ œ ์ง€์  ๋ชฉ๋ก์œผ๋กœ ๋Œ€์ฒด ํ•„์š” - ], - onChanged: (value) { - setState(() { - _controller.currentBranchId = value != null ? int.tryParse(value) : null; - }); - }, - ), - ), + style: TextStyle( + color: _controller.isFieldReadOnly('purchasePrice') + ? Colors.grey[600] : null, ), - ], - ), - - // ์ ๊ฒ€ ๋‚ ์งœ ์ •๋ณด - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: FormFieldWrapper( - label: '์ตœ๊ทผ ์ ๊ฒ€์ผ', - required: false, - child: InkWell( - onTap: () async { - final DateTime? picked = await showDatePicker( - context: context, - initialDate: _controller.lastInspectionDate ?? DateTime.now(), - firstDate: DateTime(2000), - lastDate: DateTime.now(), - ); - if (picked != null) { - setState(() { - _controller.lastInspectionDate = picked; - }); - } - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 15, - ), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade400), - borderRadius: BorderRadius.circular(4), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - _controller.lastInspectionDate != null - ? '${_controller.lastInspectionDate!.year}-${_controller.lastInspectionDate!.month.toString().padLeft(2, '0')}-${_controller.lastInspectionDate!.day.toString().padLeft(2, '0')}' - : '๋‚ ์งœ๋ฅผ ์„ ํƒํ•˜์„ธ์š”', - style: TextStyle( - color: _controller.lastInspectionDate != null - ? Colors.black87 - : Colors.grey.shade600, - ), - ), - const Icon(Icons.calendar_today, size: 16), - ], - ), - ), - ), - ), - ), - const SizedBox(width: 12), - Expanded( - child: FormFieldWrapper( - label: '๋‹ค์Œ ์ ๊ฒ€์ผ', - required: false, - child: InkWell( - onTap: () async { - final DateTime? picked = await showDatePicker( - context: context, - initialDate: _controller.nextInspectionDate ?? DateTime.now().add(const Duration(days: 365)), - firstDate: DateTime.now(), - lastDate: DateTime(2100), - ); - if (picked != null) { - setState(() { - _controller.nextInspectionDate = picked; - }); - } - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 15, - ), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade400), - borderRadius: BorderRadius.circular(4), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - _controller.nextInspectionDate != null - ? '${_controller.nextInspectionDate!.year}-${_controller.nextInspectionDate!.month.toString().padLeft(2, '0')}-${_controller.nextInspectionDate!.day.toString().padLeft(2, '0')}' - : '๋‚ ์งœ๋ฅผ ์„ ํƒํ•˜์„ธ์š”', - style: TextStyle( - color: _controller.nextInspectionDate != null - ? Colors.black87 - : Colors.grey.shade600, - ), - ), - const Icon(Icons.calendar_today, size: 16), - ], - ), - ), - ), - ), - ), - ], - ), - - // ์žฅ๋น„ ์ƒํƒœ - const SizedBox(height: 16), - FormFieldWrapper( - label: '์žฅ๋น„ ์ƒํƒœ', - required: false, - child: DropdownButtonFormField( - value: _getValidEquipmentStatus(_controller.equipmentStatus), - decoration: const InputDecoration( - hintText: '์žฅ๋น„ ์ƒํƒœ๋ฅผ ์„ ํƒํ•˜์„ธ์š”', - ), - items: const [ - DropdownMenuItem(value: 'available', child: Text('์‚ฌ์šฉ ๊ฐ€๋Šฅ')), - DropdownMenuItem(value: 'inuse', child: Text('์‚ฌ์šฉ ์ค‘')), - DropdownMenuItem(value: 'maintenance', child: Text('์œ ์ง€๋ณด์ˆ˜')), - DropdownMenuItem(value: 'disposed', child: Text('ํ๊ธฐ')), - ], - onChanged: (value) { - setState(() { - _controller.equipmentStatus = value; - }); + keyboardType: _controller.isFieldReadOnly('purchasePrice') + ? null : TextInputType.number, + inputFormatters: _controller.isFieldReadOnly('purchasePrice') + ? null : [KRWTextInputFormatter()], + onSaved: (value) { + _controller.purchasePrice = CurrencyFormatter.parseKRW(value); }, ), ), - - // ๋น„๊ณ  ์ž…๋ ฅ๋ž€ ์ถ”๊ฐ€ - const SizedBox(height: 16), - FormFieldWrapper( - label: '๋น„๊ณ ', - required: false, - child: RemarkInput( - controller: _controller.remarkController, - hint: '๋น„๊ณ ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”', - minLines: 4, - ), - ), - ], // FormSection children ๋ - ), // FormSection ๋ - ], // Column children ๋ - ), // SingleChildScrollView child ๋ - ), // Form child ๋ - ), // FormLayoutTemplate child ๋ - ), // GestureDetector ๋ - ); - }, // Consumer builder ๋ - ), // Consumer ๋ - ); // ChangeNotifierProvider.value ๋ + ], + ), + ], + ), + ), + ); } -} + + Widget _buildRemarkSection() { + return Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '๋น„๊ณ ', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + + TextFormField( + controller: _controller.remarkController, + decoration: const InputDecoration( + labelText: '๋น„๊ณ ', + hintText: '๋น„๊ณ ์‚ฌํ•ญ์„ ์ž…๋ ฅํ•˜์„ธ์š”', + border: OutlineInputBorder(), + ), + maxLines: 3, + ), + ], + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/screens/equipment/equipment_list.dart b/lib/screens/equipment/equipment_list.dart index 1dee1b3..05334e0 100644 --- a/lib/screens/equipment/equipment_list.dart +++ b/lib/screens/equipment/equipment_list.dart @@ -618,10 +618,8 @@ class _EquipmentListState extends State { Routes.equipmentInAdd, ); if (result == true) { - setState(() { - _controller.loadData(); - _controller.goToPage(1); - }); + // ์ž…๊ณ  ์™„๋ฃŒ ํ›„ ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ (์ค‘๋ณต ๋ฐฉ์ง€) + _controller.refresh(); } }, variant: ShadcnButtonVariant.primary, @@ -693,10 +691,8 @@ class _EquipmentListState extends State { Routes.equipmentInAdd, ); if (result == true) { - setState(() { - _controller.loadData(); - _controller.goToPage(1); - }); + // ์ž…๊ณ  ์™„๋ฃŒ ํ›„ ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ (์ค‘๋ณต ๋ฐฉ์ง€) + _controller.refresh(); } }, variant: ShadcnButtonVariant.primary, @@ -743,7 +739,11 @@ class _EquipmentListState extends State { totalWidth += 100; // ์นดํ…Œ๊ณ ๋ฆฌ totalWidth += 50; // ์ˆ˜๋Ÿ‰ totalWidth += 70; // ์ƒํƒœ - totalWidth += 80; // ๋‚ ์งœ + totalWidth += 80; // ์ž…์ถœ๊ณ ์ผ + totalWidth += 120; // ์ž…๊ณ ์ง€ + totalWidth += 120; // ๊ตฌ๋งค์ฒ˜ + totalWidth += 100; // ๊ตฌ๋งค์ผ + totalWidth += 100; // ๊ตฌ๋งค๊ฐ€๊ฒฉ totalWidth += 90; // ๊ด€๋ฆฌ // ์ƒ์„ธ ์ปฌ๋Ÿผ๋“ค (์กฐ๊ฑด๋ถ€) @@ -751,10 +751,8 @@ class _EquipmentListState extends State { totalWidth += 120; // ์‹œ๋ฆฌ์–ผ๋ฒˆํ˜ธ totalWidth += 120; // ๋ฐ”์ฝ”๋“œ totalWidth += 120; // ํ˜„์žฌ ์œ„์น˜ - totalWidth += 100; // ์ฐฝ๊ณ  ์œ„์น˜ + totalWidth += 100; // ์ฐฝ๊ณ  ์œ„์น˜ (์ค‘๋ณต - ์ž…๊ณ ์ง€์™€ ๋‹ค๋ฆ„) totalWidth += 100; // ์ ๊ฒ€์ผ - totalWidth += 100; // ๊ตฌ๋งค์ผ - totalWidth += 100; // ๊ตฌ๋งค๊ฐ€๊ฒฉ } // padding ์ถ”๊ฐ€ (์ขŒ์šฐ ๊ฐ 16px) @@ -853,15 +851,21 @@ class _EquipmentListState extends State { _buildHeaderCell('์ˆ˜๋Ÿ‰', flex: 1, useExpanded: useExpanded, minWidth: 50), // ์ƒํƒœ _buildHeaderCell('์ƒํƒœ', flex: 2, useExpanded: useExpanded, minWidth: 70), - // ๋‚ ์งœ - _buildHeaderCell('๋‚ ์งœ', flex: 2, useExpanded: useExpanded, minWidth: 80), + // ์ž…์ถœ๊ณ ์ผ + _buildHeaderCell('์ž…์ถœ๊ณ ์ผ', flex: 2, useExpanded: useExpanded, minWidth: 80), + // ์ž…๊ณ ์ง€ + _buildHeaderCell('์ž…๊ณ ์ง€', flex: 3, useExpanded: useExpanded, minWidth: 120), + // ๊ตฌ๋งค์ฒ˜ + _buildHeaderCell('๊ตฌ๋งค์ฒ˜', flex: 3, useExpanded: useExpanded, minWidth: 120), + // ๊ตฌ๋งค์ผ + _buildHeaderCell('๊ตฌ๋งค์ผ', flex: 2, useExpanded: useExpanded, minWidth: 100), + // ๊ตฌ๋งค๊ฐ€๊ฒฉ + _buildHeaderCell('๊ตฌ๋งค๊ฐ€๊ฒฉ', flex: 2, useExpanded: useExpanded, minWidth: 100), // ์ƒ์„ธ ์ •๋ณด (์กฐ๊ฑด๋ถ€) if (_showDetailedColumns) ...[ _buildHeaderCell('ํ˜„์žฌ ์œ„์น˜', flex: 3, useExpanded: useExpanded, minWidth: 120), _buildHeaderCell('์ฐฝ๊ณ  ์œ„์น˜', flex: 2, useExpanded: useExpanded, minWidth: 100), _buildHeaderCell('์ ๊ฒ€์ผ', flex: 2, useExpanded: useExpanded, minWidth: 100), - _buildHeaderCell('๊ตฌ๋งค์ผ', flex: 2, useExpanded: useExpanded, minWidth: 100), - _buildHeaderCell('๊ตฌ๋งค๊ฐ€๊ฒฉ', flex: 2, useExpanded: useExpanded, minWidth: 100), ], // ๊ด€๋ฆฌ _buildHeaderCell('๊ด€๋ฆฌ', flex: 2, useExpanded: useExpanded, minWidth: 90), @@ -975,13 +979,57 @@ class _EquipmentListState extends State { useExpanded: useExpanded, minWidth: 70, ), - // ๋‚ ์งœ + // ์ž…์ถœ๊ณ ์ผ _buildDataCell( - _buildDateWidget(equipment), + _buildCreatedDateWidget(equipment), flex: 2, useExpanded: useExpanded, minWidth: 80, ), + // ์ž…๊ณ ์ง€ + _buildDataCell( + Text( + equipment.warehouseLocation ?? '-', + style: ShadcnTheme.bodySmall, + ), + flex: 3, + useExpanded: useExpanded, + minWidth: 120, + ), + // ๊ตฌ๋งค์ฒ˜ (ํšŒ์‚ฌ๋ช…) + _buildDataCell( + Text( + equipment.currentCompany ?? '-', + style: ShadcnTheme.bodySmall, + ), + flex: 3, + useExpanded: useExpanded, + minWidth: 120, + ), + // ๊ตฌ๋งค์ผ + _buildDataCell( + Text( + equipment.equipment.purchaseDate != null + ? '${equipment.equipment.purchaseDate!.year}/${equipment.equipment.purchaseDate!.month.toString().padLeft(2, '0')}/${equipment.equipment.purchaseDate!.day.toString().padLeft(2, '0')}' + : '-', + style: ShadcnTheme.bodySmall, + ), + flex: 2, + useExpanded: useExpanded, + minWidth: 100, + ), + // ๊ตฌ๋งค๊ฐ€๊ฒฉ + _buildDataCell( + Text( + equipment.equipment.purchasePrice != null + ? 'โ‚ฉ${equipment.equipment.purchasePrice!.toStringAsFixed(0).replaceAllMapped(RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]},')}' + : '-', + style: ShadcnTheme.bodySmall, + ), + flex: 2, + useExpanded: useExpanded, + minWidth: 100, + ), // ์ƒ์„ธ ์ •๋ณด (์กฐ๊ฑด๋ถ€) if (_showDetailedColumns) ...[ // ํ˜„์žฌ ์œ„์น˜ (ํšŒ์‚ฌ + ์ง€์ ) @@ -1011,30 +1059,6 @@ class _EquipmentListState extends State { useExpanded: useExpanded, minWidth: 100, ), - // ๊ตฌ๋งค์ผ - _buildDataCell( - Text( - equipment.equipment.inDate != null - ? '${equipment.equipment.inDate!.year}/${equipment.equipment.inDate!.month.toString().padLeft(2, '0')}/${equipment.equipment.inDate!.day.toString().padLeft(2, '0')}' - : '-', - style: ShadcnTheme.bodySmall, - ), - flex: 2, - useExpanded: useExpanded, - minWidth: 100, - ), - // ๊ตฌ๋งค๊ฐ€๊ฒฉ - _buildDataCell( - Text( - equipment.equipment.purchasePrice != null - ? 'โ‚ฉ${equipment.equipment.purchasePrice!.toStringAsFixed(0).replaceAllMapped(RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]},')}' - : '-', - style: ShadcnTheme.bodySmall, - ), - flex: 2, - useExpanded: useExpanded, - minWidth: 100, - ), ], // ๊ด€๋ฆฌ _buildDataCell( @@ -1159,8 +1183,8 @@ class _EquipmentListState extends State { ); } - /// ๋‚ ์งœ ์œ„์ ฏ ๋นŒ๋” - Widget _buildDateWidget(UnifiedEquipment equipment) { + /// ์ž…์ถœ๊ณ ์ผ ์œ„์ ฏ ๋นŒ๋” + Widget _buildCreatedDateWidget(UnifiedEquipment equipment) { String dateStr = equipment.date.toString().substring(0, 10); return Text( dateStr, @@ -1288,23 +1312,63 @@ class _EquipmentListState extends State { return '${category.substring(0, 2)}...'; } - /// ์นดํ…Œ๊ณ ๋ฆฌ ํˆดํŒ ์œ„์ ฏ + /// ์˜์–ด ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ํ•œ๊ตญ์–ด๋กœ ๋ณ€ํ™˜ + String _translateCategory(String category) { + const Map categoryMap = { + // ๋Œ€๋ถ„๋ฅ˜ + 'Network': '๋„คํŠธ์›Œํฌ', + 'Server': '์„œ๋ฒ„', + 'Storage': '์Šคํ† ๋ฆฌ์ง€', + 'Security': '๋ณด์•ˆ', + 'Computer': '์ปดํ“จํ„ฐ', + 'Mobile': '๋ชจ๋ฐ”์ผ', + 'Printer': 'ํ”„๋ฆฐํ„ฐ', + 'Monitor': '๋ชจ๋‹ˆํ„ฐ', + 'Peripheral': '์ฃผ๋ณ€๊ธฐ๊ธฐ', + // ์ค‘๋ถ„๋ฅ˜ + 'Router': '๋ผ์šฐํ„ฐ', + 'Switch': '์Šค์œ„์น˜', + 'Firewall': '๋ฐฉํ™”๋ฒฝ', + 'Laptop': '๋…ธํŠธ๋ถ', + 'Desktop': '๋ฐ์Šคํฌํ†ฑ', + 'Tablet': 'ํƒœ๋ธ”๋ฆฟ', + 'Smartphone': '์Šค๋งˆํŠธํฐ', + 'Scanner': '์Šค์บ๋„ˆ', + 'Keyboard': 'ํ‚ค๋ณด๋“œ', + 'Mouse': '๋งˆ์šฐ์Šค', + // ์†Œ๋ถ„๋ฅ˜ ์˜ˆ์‹œ + 'Wireless': '๋ฌด์„ ', + 'Wired': '์œ ์„ ', + 'Gaming': '๊ฒŒ์ด๋ฐ', + 'Office': '์‚ฌ๋ฌด์šฉ', + }; + + return categoryMap[category] ?? category; + } + + /// ์นดํ…Œ๊ณ ๋ฆฌ ํˆดํŒ ์œ„์ ฏ (ํ•œ๊ตญ์–ด ๋ณ€ํ™˜ ์ ์šฉ) Widget _buildCategoryWithTooltip(UnifiedEquipment equipment) { + // ์˜์–ดโ†’ํ•œ๊ตญ์–ด ๋ณ€ํ™˜ ์ ์šฉ + final translatedCategory = _translateCategory(equipment.equipment.category); + final translatedSubCategory = _translateCategory(equipment.equipment.subCategory); + final translatedSubSubCategory = _translateCategory(equipment.equipment.subSubCategory); + final fullCategory = EquipmentDisplayHelper.formatCategory( - equipment.equipment.category, - equipment.equipment.subCategory, - equipment.equipment.subSubCategory, + translatedCategory, + translatedSubCategory, + translatedSubSubCategory, ); + // ์ถ•์•ฝ ํ‘œ๊ธฐ ์ ์šฉ - ๋น„์–ด์žˆ์ง€ ์•Š์€ ์นดํ…Œ๊ณ ๋ฆฌ๋งŒ ํ‘œ์‹œ final List parts = []; - if (equipment.equipment.category.isNotEmpty) { - parts.add(_shortenCategory(equipment.equipment.category)); + if (translatedCategory.isNotEmpty) { + parts.add(_shortenCategory(translatedCategory)); } - if (equipment.equipment.subCategory.isNotEmpty) { - parts.add(_shortenCategory(equipment.equipment.subCategory)); + if (translatedSubCategory.isNotEmpty) { + parts.add(_shortenCategory(translatedSubCategory)); } - if (equipment.equipment.subSubCategory.isNotEmpty) { - parts.add(_shortenCategory(equipment.equipment.subSubCategory)); + if (translatedSubSubCategory.isNotEmpty) { + parts.add(_shortenCategory(translatedSubSubCategory)); } final shortCategory = parts.join(' > '); diff --git a/lib/services/equipment_service.dart b/lib/services/equipment_service.dart index 454ae00..723b4ce 100644 --- a/lib/services/equipment_service.dart +++ b/lib/services/equipment_service.dart @@ -130,7 +130,12 @@ class EquipmentService { Future createEquipment(Equipment equipment) async { try { final request = CreateEquipmentRequest( - equipmentNumber: 'EQ-${DateTime.now().millisecondsSinceEpoch}', // ์ž๋™ ์ƒ์„ฑ ๋ฒˆํ˜ธ + // ๐Ÿ”ง [BUG FIX] ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์žฅ๋น„ ๋ฒˆํ˜ธ๋ฅผ ์šฐ์„  ์‚ฌ์šฉ, ์—†์œผ๋ฉด ์ž๋™ ์ƒ์„ฑ + // ๊ธฐ์กด: ํ•ญ์ƒ ํƒ€์ž„์Šคํƒฌํ”„ ๊ธฐ๋ฐ˜ ์ž๋™ ์ƒ์„ฑ์œผ๋กœ ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๋ฌด์‹œ + // ์ˆ˜์ •: equipment.equipmentNumber๊ฐ€ ์žˆ์œผ๋ฉด ์šฐ์„  ์‚ฌ์šฉ, null/empty๋ฉด ์ž๋™ ์ƒ์„ฑ + equipmentNumber: equipment.equipmentNumber?.isNotEmpty == true + ? equipment.equipmentNumber! // ์‚ฌ์šฉ์ž ์ž…๋ ฅ๊ฐ’ ์‚ฌ์šฉ + : 'EQ-${DateTime.now().millisecondsSinceEpoch}', // ์ž๋™ ์ƒ์„ฑ fallback category1: equipment.category, category2: equipment.subCategory, category3: equipment.subSubCategory, @@ -140,7 +145,11 @@ class EquipmentService { barcode: equipment.barcode, purchaseDate: equipment.inDate, purchasePrice: equipment.purchasePrice, - companyId: equipment.currentCompanyId, + // ๐Ÿ”ง [BUG FIX] currentCompanyId โ†’ companyId ํ•„๋“œ ์ˆ˜์ • + // ๋ฌธ์ œ: Controller์—์„œ selectedCompanyId๋ฅผ equipment.companyId๋กœ ์„ค์ •ํ•˜๋Š”๋ฐ + // EquipmentService์—์„œ equipment.currentCompanyId๋ฅผ ์ฐธ์กฐํ•ด์„œ null ์ „์†ก + // ํ•ด๊ฒฐ: equipment.companyId ์ฐธ์กฐ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ์‹ค์ œ ์„ ํƒ๊ฐ’ ์ „์†ก + companyId: equipment.companyId, warehouseLocationId: equipment.warehouseLocationId, lastInspectionDate: equipment.lastInspectionDate, nextInspectionDate: equipment.nextInspectionDate, @@ -196,14 +205,14 @@ class EquipmentService { modelName: equipment.name.isNotEmpty ? equipment.name : null, // ์‹ค์ œ ์žฅ๋น„๋ช… serialNumber: equipment.serialNumber?.isNotEmpty == true ? equipment.serialNumber : null, barcode: equipment.barcode?.isNotEmpty == true ? equipment.barcode : null, - purchaseDate: equipment.inDate, + purchaseDate: equipment.purchaseDate, purchasePrice: equipment.purchasePrice, status: (equipment.equipmentStatus != null && equipment.equipmentStatus != 'null' && equipment.equipmentStatus!.isNotEmpty) ? EquipmentStatusConverter.clientToServer(equipment.equipmentStatus) : null, - companyId: equipment.currentCompanyId, + companyId: equipment.companyId, warehouseLocationId: equipment.warehouseLocationId, lastInspectionDate: equipment.lastInspectionDate, nextInspectionDate: equipment.nextInspectionDate, @@ -369,12 +378,7 @@ class EquipmentService { } Equipment _convertResponseToEquipment(EquipmentResponse response) { - print('DEBUG [_convertResponseToEquipment] Converting response to Equipment'); - print('DEBUG [_convertResponseToEquipment] response.manufacturer="${response.manufacturer}"'); - print('DEBUG [_convertResponseToEquipment] response.modelName="${response.modelName}"'); - print('DEBUG [_convertResponseToEquipment] response.category1="${response.category1}"'); - - final equipment = Equipment( + return Equipment( id: response.id, manufacturer: response.manufacturer, name: response.modelName ?? '', // modelName์ด ์‹ค์ œ ์žฅ๋น„๋ช… @@ -386,21 +390,22 @@ class EquipmentService { quantity: 1, // Default quantity, actual quantity should be tracked in history inDate: response.purchaseDate, remark: response.remark, - // ๋ฐฑ์—”๋“œ API ์ƒˆ๋กœ์šด ํ•„๋“œ๋“ค ๋งคํ•‘ + // ๋ฐฑ์—”๋“œ API ์ƒˆ๋กœ์šด ํ•„๋“œ๋“ค ๋งคํ•‘ - ๋ฐฑ์—”๋“œ ์™„์ „ ํ˜ธํ™˜ purchasePrice: response.purchasePrice != null ? double.tryParse(response.purchasePrice!) : null, currentCompanyId: response.companyId, warehouseLocationId: response.warehouseLocationId, + companyId: response.companyId, lastInspectionDate: response.lastInspectionDate, nextInspectionDate: response.nextInspectionDate, equipmentStatus: response.status, - // Warranty information would need to be fetched from license API if available + // ๋ฐฑ์—”๋“œ API ์™„์ „ ํ˜ธํ™˜ ํ•„๋“œ๋“ค + equipmentNumber: response.equipmentNumber, + modelName: response.modelName, + category1: response.category1, + category2: response.category2, + category3: response.category3, + purchaseDate: response.purchaseDate, ); - - print('DEBUG [_convertResponseToEquipment] Equipment created'); - print('DEBUG [_convertResponseToEquipment] equipment.manufacturer="${equipment.manufacturer}"'); - print('DEBUG [_convertResponseToEquipment] equipment.name="${equipment.name}"'); - - return equipment; } // ์žฅ๋น„ ์ƒํƒœ ์ƒ์ˆ˜ diff --git a/lib/utils/currency_formatter.dart b/lib/utils/currency_formatter.dart new file mode 100644 index 0000000..b1055bd --- /dev/null +++ b/lib/utils/currency_formatter.dart @@ -0,0 +1,64 @@ +import 'package:flutter/services.dart'; +import 'package:intl/intl.dart'; + +/// KRW ํ†ตํ™” ํฌ๋งทํŒ… ๊ด€๋ จ ์œ ํ‹ธ๋ฆฌํ‹ฐ +class CurrencyFormatter { + static final NumberFormat _formatter = NumberFormat('#,###', 'ko_KR'); + + /// ์ˆซ์ž๋ฅผ KRW ํ†ตํ™” ํ˜•์‹์œผ๋กœ ํฌ๋งทํŒ… (โ‚ฉ2,000,000) + static String formatKRW(double? value) { + if (value == null || value == 0) return ''; + return 'โ‚ฉ${_formatter.format(value.toInt())}'; + } + + /// ํ†ตํ™” ํฌ๋งทํŒ…๋œ ๋ฌธ์ž์—ด์—์„œ ์ˆซ์ž๋งŒ ์ถ”์ถœ + static double? parseKRW(String? text) { + if (text == null || text.isEmpty) return null; + + // โ‚ฉ, ์‰ผํ‘œ ์ œ๊ฑฐํ•˜๊ณ  ์ˆซ์ž๋งŒ ์ถ”์ถœ + final cleanText = text.replaceAll(RegExp(r'[โ‚ฉ,]'), ''); + return double.tryParse(cleanText); + } +} + +/// KRW ํ†ตํ™” ์ž…๋ ฅ์„ ์œ„ํ•œ TextInputFormatter +class KRWTextInputFormatter extends TextInputFormatter { + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, + TextEditingValue newValue, + ) { + // ๋นˆ ๋ฌธ์ž์—ด์ธ ๊ฒฝ์šฐ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ + if (newValue.text.isEmpty) { + return newValue; + } + + // ์ˆซ์ž๋งŒ ์ถ”์ถœ + final numericText = newValue.text.replaceAll(RegExp(r'[^\d]'), ''); + + // ์ˆซ์ž๊ฐ€ ์—†์œผ๋ฉด ๋นˆ ๋ฌธ์ž์—ด + if (numericText.isEmpty) { + return const TextEditingValue( + text: '', + selection: TextSelection.collapsed(offset: 0), + ); + } + + // ์ˆซ์ž ํŒŒ์‹ฑ ๋ฐ ํฌ๋งทํŒ… + final numericValue = double.tryParse(numericText); + if (numericValue == null) { + return oldValue; + } + + // KRW ํฌ๋งทํŒ… ์ ์šฉ + final formattedText = CurrencyFormatter.formatKRW(numericValue); + + // ์ปค์„œ ์œ„์น˜ ๊ณ„์‚ฐ (๋งจ ๋์œผ๋กœ) + final selectionOffset = formattedText.length; + + return TextEditingValue( + text: formattedText, + selection: TextSelection.collapsed(offset: selectionOffset), + ); + } +} \ No newline at end of file diff --git a/test/utils/currency_formatter_test.dart b/test/utils/currency_formatter_test.dart new file mode 100644 index 0000000..7e58636 --- /dev/null +++ b/test/utils/currency_formatter_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:superport/utils/currency_formatter.dart'; + +void main() { + group('CurrencyFormatter Tests', () { + test('formatKRW should format number with Korean won symbol and commas', () { + expect(CurrencyFormatter.formatKRW(2000000), 'โ‚ฉ2,000,000'); + expect(CurrencyFormatter.formatKRW(1000), 'โ‚ฉ1,000'); + expect(CurrencyFormatter.formatKRW(100), 'โ‚ฉ100'); + expect(CurrencyFormatter.formatKRW(0), ''); + expect(CurrencyFormatter.formatKRW(null), ''); + }); + + test('parseKRW should extract numeric value from formatted string', () { + expect(CurrencyFormatter.parseKRW('โ‚ฉ2,000,000'), 2000000); + expect(CurrencyFormatter.parseKRW('โ‚ฉ1,000'), 1000); + expect(CurrencyFormatter.parseKRW('โ‚ฉ100'), 100); + expect(CurrencyFormatter.parseKRW('2000000'), 2000000); + expect(CurrencyFormatter.parseKRW(''), null); + expect(CurrencyFormatter.parseKRW(null), null); + }); + }); +} \ No newline at end of file