고객사 목록 쿼리스트링 연동 및 공통 JSON 파서 도입

This commit is contained in:
JiWoong Sul
2025-09-25 20:13:46 +09:00
parent 8a6ad1e81b
commit 900990c46b
27 changed files with 1458 additions and 176 deletions

View File

@@ -1,4 +1,5 @@
import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/core/common/utils/json_utils.dart';
import '../../domain/entities/warehouse.dart';
@@ -75,16 +76,16 @@ class WarehouseDto {
);
static PaginatedResult<Warehouse> parsePaginated(Map<String, dynamic>? json) {
final items = (json?['items'] as List<dynamic>? ?? [])
.whereType<Map<String, dynamic>>()
final rawItems = JsonUtils.extractList(json, keys: const ['items']);
final items = rawItems
.map(WarehouseDto.fromJson)
.map((dto) => dto.toEntity())
.toList();
.toList(growable: false);
return PaginatedResult<Warehouse>(
items: items,
page: json?['page'] as int? ?? 1,
pageSize: json?['page_size'] as int? ?? items.length,
total: json?['total'] as int? ?? items.length,
page: JsonUtils.readInt(json, 'page', fallback: 1),
pageSize: JsonUtils.readInt(json, 'page_size', fallback: items.length),
total: JsonUtils.readInt(json, 'total', fallback: items.length),
);
}
}

View File

@@ -5,6 +5,8 @@ import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:superport_v2/core/constants/app_sections.dart';
import 'package:superport_v2/widgets/app_layout.dart';
import 'package:superport_v2/widgets/components/filter_bar.dart';
import 'package:superport_v2/features/util/postal_search/presentation/models/postal_search_result.dart';
import 'package:superport_v2/features/util/postal_search/presentation/widgets/postal_search_dialog.dart';
import '../../../../../core/config/environment.dart';
import '../../../../../widgets/spec_page.dart';
@@ -137,7 +139,8 @@ class _WarehouseEnabledPageState extends State<_WarehouseEnabledPage> {
? false
: (result.page * result.pageSize) < result.total;
final showReset = _searchController.text.isNotEmpty ||
final showReset =
_searchController.text.isNotEmpty ||
_controller.statusFilter != WarehouseStatusFilter.all;
return AppLayout(
@@ -253,27 +256,25 @@ class _WarehouseEnabledPageState extends State<_WarehouseEnabledPage> {
child: Center(child: CircularProgressIndicator()),
)
: warehouses.isEmpty
? Padding(
padding: const EdgeInsets.all(32),
child: Text(
'조건에 맞는 창고가 없습니다.',
style: theme.textTheme.muted,
),
)
: _WarehouseTable(
warehouses: warehouses,
dateFormat: _dateFormat,
onEdit: _controller.isSubmitting
? null
: (warehouse) =>
_openWarehouseForm(context, warehouse: warehouse),
onDelete: _controller.isSubmitting
? null
: _confirmDelete,
onRestore: _controller.isSubmitting
? null
: _restoreWarehouse,
),
? Padding(
padding: const EdgeInsets.all(32),
child: Text(
'조건에 맞는 창고가 없습니다.',
style: theme.textTheme.muted,
),
)
: _WarehouseTable(
warehouses: warehouses,
dateFormat: _dateFormat,
onEdit: _controller.isSubmitting
? null
: (warehouse) =>
_openWarehouseForm(context, warehouse: warehouse),
onDelete: _controller.isSubmitting ? null : _confirmDelete,
onRestore: _controller.isSubmitting
? null
: _restoreWarehouse,
),
),
);
},
@@ -321,6 +322,17 @@ class _WarehouseEnabledPageState extends State<_WarehouseEnabledPage> {
text: existing?.addressDetail ?? '',
);
final noteController = TextEditingController(text: existing?.note ?? '');
final existingZipcode = existing?.zipcode;
final selectedPostalNotifier = ValueNotifier<PostalSearchResult?>(
existingZipcode == null
? null
: PostalSearchResult(
zipcode: existingZipcode.zipcode,
sido: existingZipcode.sido,
sigungu: existingZipcode.sigungu,
roadName: existingZipcode.roadName,
),
);
final isActiveNotifier = ValueNotifier<bool>(existing?.isActive ?? true);
final saving = ValueNotifier<bool>(false);
final codeError = ValueNotifier<String?>(null);
@@ -332,6 +344,30 @@ class _WarehouseEnabledPageState extends State<_WarehouseEnabledPage> {
final theme = ShadTheme.of(dialogContext);
final materialTheme = Theme.of(dialogContext);
final navigator = Navigator.of(dialogContext);
Future<void> openPostalSearch() async {
final keyword = zipcodeController.text.trim();
final result = await showPostalSearchDialog(
dialogContext,
initialKeyword: keyword.isEmpty ? null : keyword,
);
if (result == null) {
return;
}
zipcodeController
..text = result.zipcode
..selection = TextSelection.collapsed(
offset: result.zipcode.length,
);
selectedPostalNotifier.value = result;
if (result.fullAddress.isNotEmpty) {
addressController
..text = result.fullAddress
..selection = TextSelection.collapsed(
offset: addressController.text.length,
);
}
}
return Dialog(
insetPadding: const EdgeInsets.all(24),
clipBehavior: Clip.antiAlias,
@@ -488,9 +524,49 @@ class _WarehouseEnabledPageState extends State<_WarehouseEnabledPage> {
const SizedBox(height: 16),
_FormField(
label: '우편번호',
child: ShadInput(
controller: zipcodeController,
placeholder: const Text('예: 06000'),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: ShadInput(
controller: zipcodeController,
placeholder: const Text('예: 06000'),
keyboardType: TextInputType.number,
),
),
const SizedBox(width: 8),
ShadButton.outline(
onPressed: saving.value
? null
: openPostalSearch,
child: const Text('검색'),
),
],
),
const SizedBox(height: 8),
ValueListenableBuilder<PostalSearchResult?>(
valueListenable: selectedPostalNotifier,
builder: (_, selection, __) {
if (selection == null) {
return Text(
'검색 버튼을 눌러 주소를 선택하세요.',
style: theme.textTheme.small.copyWith(
color: theme.colorScheme.mutedForeground,
),
);
}
final fullAddress = selection.fullAddress;
return Text(
fullAddress.isEmpty
? '선택한 우편번호에 주소 정보가 없습니다.'
: fullAddress,
style: theme.textTheme.small,
);
},
),
],
),
),
const SizedBox(height: 16),
@@ -554,6 +630,7 @@ class _WarehouseEnabledPageState extends State<_WarehouseEnabledPage> {
zipcodeController.dispose();
addressController.dispose();
noteController.dispose();
selectedPostalNotifier.dispose();
isActiveNotifier.dispose();
saving.dispose();
codeError.dispose();
@@ -566,6 +643,7 @@ class _WarehouseEnabledPageState extends State<_WarehouseEnabledPage> {
zipcodeController.dispose();
addressController.dispose();
noteController.dispose();
selectedPostalNotifier.dispose();
isActiveNotifier.dispose();
saving.dispose();
codeError.dispose();