전역 구조 리팩터링 및 테스트 확장

This commit is contained in:
JiWoong Sul
2025-09-29 01:51:47 +09:00
parent c00c0c9ab2
commit fef7108479
70 changed files with 7709 additions and 3185 deletions

View File

@@ -1,25 +1,113 @@
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import '../../../../../widgets/spec_page.dart';
import '../../../../../core/constants/app_sections.dart';
import '../../../../../widgets/app_layout.dart';
import '../models/postal_search_result.dart';
import '../widgets/postal_search_dialog.dart';
class PostalSearchPage extends StatelessWidget {
class PostalSearchPage extends StatefulWidget {
const PostalSearchPage({super.key});
@override
State<PostalSearchPage> createState() => _PostalSearchPageState();
}
class _PostalSearchPageState extends State<PostalSearchPage> {
PostalSearchResult? _lastSelection;
@override
Widget build(BuildContext context) {
return const SpecPage(
final theme = ShadTheme.of(context);
return AppLayout(
title: '우편번호 검색',
summary: '모달 기반 우편번호 검색 UI 구성을 정의합니다.',
sections: [
SpecSection(
title: '모달 구성',
items: [
'검색어 [Text] 입력 필드',
'결과 리스트: 우편번호 | 시도 | 시군구 | 도로명 | 건물번호',
'선택 시 호출 화면에 우편번호/주소 전달',
],
),
subtitle: '창고/고객사 등 주소 입력 폼에서 재사용되는 검색 모달입니다.',
breadcrumbs: const [
AppBreadcrumbItem(label: '대시보드', path: dashboardRoutePath),
AppBreadcrumbItem(label: '유틸리티', path: '/utilities/postal-search'),
AppBreadcrumbItem(label: '우편번호 검색'),
],
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 520),
child: ShadCard(
title: Text('우편번호 검색 모달 미리보기', style: theme.textTheme.h3),
description: Text(
'검색 버튼을 눌러 모달 UI를 확인하세요. 검색 API 연동은 이후 단계에서 진행됩니다.',
style: theme.textTheme.muted,
),
footer: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ShadButton(
leading: const Icon(LucideIcons.search, size: 16),
onPressed: () async {
final result = await showPostalSearchDialog(context);
if (result != null && mounted) {
setState(() {
_lastSelection = result;
});
}
},
child: const Text('모달 열기'),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'우편번호를 검색한 뒤 결과 행을 클릭하면 선택한 주소가 폼에 채워집니다.',
style: theme.textTheme.p,
),
const SizedBox(height: 16),
if (_lastSelection == null)
Text('선택한 주소가 없습니다.', style: theme.textTheme.muted)
else
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: theme.colorScheme.border),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'선택된 우편번호',
style: theme.textTheme.small.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
Text(
_lastSelection!.zipcode,
style: theme.textTheme.h4,
),
const SizedBox(height: 12),
Text(
'주소 구성요소',
style: theme.textTheme.small.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
Text(
_lastSelection!.fullAddress.isEmpty
? '주소 정보가 제공되지 않았습니다.'
: _lastSelection!.fullAddress,
style: theme.textTheme.p,
),
],
),
),
],
),
),
),
),
);
}
}

View File

@@ -255,7 +255,14 @@ class _PostalSearchDialogState extends State<_PostalSearchDialog> {
],
],
onRowTap: (index) {
navigator.pop(_results[index]);
if (_results.isEmpty) {
return;
}
final adjustedIndex = (index - 1).clamp(
0,
_results.length - 1,
);
navigator.pop(_results[adjustedIndex]);
},
emptyLabel: '검색 결과가 없습니다.',
),