계정 정보 다이얼로그 추가 및 전체 목록 페치 개선

This commit is contained in:
JiWoong Sul
2025-10-22 01:05:47 +09:00
parent 6b58effc83
commit f4dc83d441
44 changed files with 1636 additions and 362 deletions

View File

@@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/core/network/failure.dart';
import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/core/common/utils/pagination_utils.dart';
import '../../../vendor/domain/entities/vendor.dart';
import '../../../vendor/domain/repositories/vendor_repository.dart';
@@ -61,13 +62,24 @@ class ProductController extends ChangeNotifier {
_errorMessage = null;
notifyListeners();
try {
final previous = _result;
final int resolvedPage;
if (page < 1) {
resolvedPage = 1;
} else if (previous != null && previous.pageSize > 0) {
final calculated = (previous.total / previous.pageSize).ceil();
final maxPage = calculated < 1 ? 1 : calculated;
resolvedPage = page > maxPage ? maxPage : page;
} else {
resolvedPage = page;
}
final isActive = switch (_statusFilter) {
ProductStatusFilter.all => null,
ProductStatusFilter.activeOnly => true,
ProductStatusFilter.inactiveOnly => false,
};
final response = await _productRepository.list(
page: page,
page: resolvedPage,
pageSize: _pageSize,
query: _query.isEmpty ? null : _query,
vendorId: _vendorFilter,
@@ -92,10 +104,16 @@ class ProductController extends ChangeNotifier {
_isLoadingLookups = true;
notifyListeners();
try {
final vendorResult = await _vendorRepository.list(page: 1, pageSize: 100);
final uomResult = await _uomRepository.list(page: 1, pageSize: 100);
_vendorOptions = vendorResult.items;
_uomOptions = uomResult.items;
final vendors = await fetchAllPaginatedItems<Vendor>(
request: (page, pageSize) =>
_vendorRepository.list(page: page, pageSize: pageSize),
);
final uoms = await fetchAllPaginatedItems<Uom>(
request: (page, pageSize) =>
_uomRepository.list(page: page, pageSize: pageSize),
);
_vendorOptions = vendors;
_uomOptions = uoms;
} catch (error) {
final failure = Failure.from(error);
_errorMessage = failure.describe();

View File

@@ -125,9 +125,10 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
final error = _controller.errorMessage;
if (error != null && error != _lastError && mounted) {
_lastError = error;
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(error)));
final messenger = ScaffoldMessenger.maybeOf(context);
if (messenger != null) {
messenger.showSnackBar(SnackBar(content: Text(error)));
}
_controller.clearError();
}
}
@@ -154,7 +155,7 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
final currentPage = result?.page ?? 1;
final totalPages = result == null || result.pageSize == 0
? 1
: (result.total / result.pageSize).ceil().clamp(1, 9999);
: (result.total / result.pageSize).ceil().clamp(1, 9999) as int;
final hasNext = result == null
? false
: (result.page * result.pageSize) < result.total;
@@ -316,6 +317,14 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
),
Row(
children: [
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _goToPage(1),
child: const Text('처음'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
@@ -331,6 +340,15 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
: () => _goToPage(currentPage + 1),
child: const Text('다음'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoading || currentPage >= totalPages
? null
: () => _goToPage(totalPages),
child: const Text('마지막'),
),
],
),
],
@@ -428,8 +446,18 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
}
void _goToPage(int page) {
final result = _controller.result;
final int totalPages;
if (result == null || result.pageSize == 0) {
totalPages = 1;
} else {
final calculated = (result.total / result.pageSize).ceil();
totalPages = calculated < 1 ? 1 : calculated;
}
if (page < 1) {
page = 1;
} else if (page > totalPages) {
page = totalPages;
}
_updateRoute(page: page);
}
@@ -579,7 +607,10 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
isActive: isActiveNotifier.value,
note: note.isEmpty ? null : note,
);
final navigator = Navigator.of(context);
final navigator = Navigator.of(
context,
rootNavigator: true,
);
final response = isEdit
? await _controller.update(productId!, input)
: await _controller.create(input);
@@ -602,7 +633,8 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
return ShadButton.ghost(
onPressed: isSaving
? null
: () => Navigator.of(context).pop(false),
: () =>
Navigator.of(context, rootNavigator: true).pop(false),
child: const Text('취소'),
);
},
@@ -835,11 +867,13 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
title: '제품 삭제',
description: '"${product.productName}" 제품을 삭제하시겠습니까?',
primaryAction: ShadButton.destructive(
onPressed: () => Navigator.of(context).pop(true),
onPressed: () =>
Navigator.of(context, rootNavigator: true).pop(true),
child: const Text('삭제'),
),
secondaryAction: ShadButton.ghost(
onPressed: () => Navigator.of(context).pop(false),
onPressed: () =>
Navigator.of(context, rootNavigator: true).pop(false),
child: const Text('취소'),
),
),
@@ -862,10 +896,14 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
}
void _showSnack(String message) {
if (!mounted) return;
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(message)));
if (!mounted) {
return;
}
final messenger = ScaffoldMessenger.maybeOf(context);
if (messenger == null) {
return;
}
messenger.showSnackBar(SnackBar(content: Text(message)));
}
String _formatDateTime(DateTime? value) {

View File

@@ -201,25 +201,16 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
}
void _setSelection(Uom? uom, {bool notify = true}) {
_selected = uom;
if (uom != null && !_baseOptions.any((item) => item.id == uom.id)) {
_baseOptions.add(uom);
}
_isApplyingText = true;
if (uom == null) {
_controller.clear();
if (notify) {
widget.onSelected(null);
}
} else {
_controller
..text = uom.uomName
..selection = TextSelection.collapsed(offset: uom.uomName.length);
if (notify) {
widget.onSelected(uom.id);
setState(() {
_selected = uom;
if (uom != null && !_baseOptions.any((item) => item.id == uom.id)) {
_baseOptions.add(uom);
}
_applyControllerText(uom?.uomName ?? '');
});
if (notify) {
widget.onSelected(uom?.id);
}
_isApplyingText = false;
}
@override
@@ -247,6 +238,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
displayStringForOption: (option) => option.uomName,
onSelected: (uom) => _setSelection(uom),
fieldViewBuilder: (context, textController, focusNode, onFieldSubmitted) {
assert(identical(textController, _controller));
final placeholder = widget.placeholder ?? const Text('단위를 선택하세요');
return Stack(
alignment: Alignment.centerRight,
@@ -312,4 +304,11 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
},
);
}
void _applyControllerText(String value) {
_isApplyingText = true;
_controller.text = value;
_controller.selection = TextSelection.collapsed(offset: value.length);
_isApplyingText = false;
}
}

View File

@@ -209,26 +209,20 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
}
void _setSelection(Vendor? vendor, {bool notify = true}) {
_selected = vendor;
if (vendor != null && !_baseOptions.any((item) => item.id == vendor.id)) {
_baseOptions.add(vendor);
}
_isApplyingText = true;
if (vendor == null) {
_controller.clear();
if (notify) {
widget.onSelected(null);
setState(() {
_selected = vendor;
if (vendor != null && !_baseOptions.any((item) => item.id == vendor.id)) {
_baseOptions.add(vendor);
}
} else {
final label = _displayLabel(vendor);
_controller
..text = label
..selection = TextSelection.collapsed(offset: label.length);
if (notify) {
widget.onSelected(vendor.id);
if (vendor == null) {
_applyControllerText('');
} else {
_applyControllerText(_displayLabel(vendor));
}
});
if (notify) {
widget.onSelected(vendor?.id);
}
_isApplyingText = false;
}
String _displayLabel(Vendor vendor) {
@@ -261,6 +255,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
displayStringForOption: _displayLabel,
onSelected: (vendor) => _setSelection(vendor),
fieldViewBuilder: (context, textController, focusNode, onFieldSubmitted) {
assert(identical(textController, _controller));
final placeholder = widget.placeholder ?? const Text('제조사를 선택하세요');
return Stack(
alignment: Alignment.centerRight,
@@ -326,4 +321,11 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
},
);
}
void _applyControllerText(String value) {
_isApplyingText = true;
_controller.text = value;
_controller.selection = TextSelection.collapsed(offset: value.length);
_isApplyingText = false;
}
}