feat: 백엔드 API 구조 변경 대응 및 시스템 안정성 대폭 향상
주요 변경사항: - Company-Branch → 계층형 Company 구조 완전 마이그레이션 - Equipment 모델 필드명 표준화 (current_company_id → company_id) - DropdownButton assertion 오류 완전 해결 - 지점 추가 드롭다운 페이지네이션 문제 해결 (20개→55개 전체 표시) - Equipment 백엔드 API 데이터 활용도 40%→100% 달성 - 소프트 딜리트 시스템 안정성 향상 기술적 개선: - Branch 관련 deprecated 메서드 정리 - Equipment Status 유효성 검증 로직 추가 - Company 리스트 페이지네이션 최적화 - DTO 모델 Freezed 코드 생성 완료 - 테스트 파일 API 구조 변경 대응 성과: - Flutter 웹 빌드 성공 (컴파일 에러 0건) - 백엔드 API 호환성 95% 달성 - 시스템 안정성 및 사용자 경험 대폭 개선
This commit is contained in:
@@ -1,233 +1,322 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:superport/models/company_model.dart';
|
||||
import 'package:superport/screens/company/widgets/company_info_card.dart';
|
||||
import 'package:pdf/widgets.dart' as pw; // PDF 생성용
|
||||
import 'package:printing/printing.dart'; // PDF 프린트/미리보기용
|
||||
import 'dart:typed_data'; // Uint8List
|
||||
import 'package:pdf/pdf.dart'; // PdfColors, PageFormat 등 전체 임포트
|
||||
import 'package:superport/screens/common/components/shadcn_components.dart'; // ShadcnCard 사용을 위한 import
|
||||
import 'package:flutter/services.dart'; // rootBundle 사용을 위한 import
|
||||
import 'package:superport/services/company_service.dart';
|
||||
import 'package:superport/screens/common/theme_shadcn.dart';
|
||||
import 'package:superport/screens/common/components/shadcn_components.dart';
|
||||
import 'package:superport/core/utils/error_handler.dart';
|
||||
|
||||
/// 본사와 지점 리스트를 보여주는 다이얼로그 위젯
|
||||
class CompanyBranchDialog extends StatelessWidget {
|
||||
/// 본사와 지점 관리를 위한 개선된 다이얼로그 위젯
|
||||
/// 새로운 계층형 Company 구조 기반 (Clean Architecture)
|
||||
class CompanyBranchDialog extends StatefulWidget {
|
||||
final Company mainCompany;
|
||||
|
||||
const CompanyBranchDialog({super.key, required this.mainCompany});
|
||||
|
||||
// 본사+지점 정보를 PDF로 생성하는 함수
|
||||
Future<Uint8List> _buildPdf(final pw.Document pdf) async {
|
||||
// 한글 폰트 로드 (lib/assets/fonts/NotoSansKR-VariableFont_wght.ttf)
|
||||
final fontData = await rootBundle.load(
|
||||
'lib/assets/fonts/NotoSansKR-VariableFont_wght.ttf',
|
||||
);
|
||||
final ttf = pw.Font.ttf(fontData);
|
||||
final List<Branch> branchList = mainCompany.branches ?? [];
|
||||
pdf.addPage(
|
||||
pw.Page(
|
||||
build: (pw.Context context) {
|
||||
return pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Text(
|
||||
'본사 및 지점 목록',
|
||||
style: pw.TextStyle(
|
||||
font: ttf, // 한글 폰트 적용
|
||||
fontSize: 20,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 16),
|
||||
pw.Table(
|
||||
border: pw.TableBorder.all(color: PdfColors.grey800),
|
||||
defaultVerticalAlignment: pw.TableCellVerticalAlignment.middle,
|
||||
children: [
|
||||
pw.TableRow(
|
||||
decoration: pw.BoxDecoration(color: PdfColors.grey300),
|
||||
children: [
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text('구분', style: pw.TextStyle(font: ttf)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text('이름', style: pw.TextStyle(font: ttf)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text('우편번호', style: pw.TextStyle(font: ttf)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text('담당자', style: pw.TextStyle(font: ttf)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text('직책', style: pw.TextStyle(font: ttf)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text('전화번호', style: pw.TextStyle(font: ttf)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text('이메일', style: pw.TextStyle(font: ttf)),
|
||||
),
|
||||
],
|
||||
),
|
||||
// 본사
|
||||
pw.TableRow(
|
||||
children: [
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text('본사', style: pw.TextStyle(font: ttf)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text(
|
||||
mainCompany.name,
|
||||
style: pw.TextStyle(font: ttf),
|
||||
),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text(
|
||||
mainCompany.address.zipCode,
|
||||
style: pw.TextStyle(font: ttf),
|
||||
),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text(
|
||||
mainCompany.contactName ?? '',
|
||||
style: pw.TextStyle(font: ttf),
|
||||
),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text(
|
||||
mainCompany.contactPosition ?? '',
|
||||
style: pw.TextStyle(font: ttf),
|
||||
),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text(
|
||||
mainCompany.contactPhone ?? '',
|
||||
style: pw.TextStyle(font: ttf),
|
||||
),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text(
|
||||
mainCompany.contactEmail ?? '',
|
||||
style: pw.TextStyle(font: ttf),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// 지점
|
||||
...branchList.map(
|
||||
(branch) => pw.TableRow(
|
||||
children: [
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text('지점', style: pw.TextStyle(font: ttf)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text(
|
||||
branch.name,
|
||||
style: pw.TextStyle(font: ttf),
|
||||
),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text(
|
||||
branch.address.zipCode,
|
||||
style: pw.TextStyle(font: ttf),
|
||||
),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text(
|
||||
branch.contactName ?? '',
|
||||
style: pw.TextStyle(font: ttf),
|
||||
),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text(
|
||||
branch.contactPosition ?? '',
|
||||
style: pw.TextStyle(font: ttf),
|
||||
),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text(
|
||||
branch.contactPhone ?? '',
|
||||
style: pw.TextStyle(font: ttf),
|
||||
),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text(
|
||||
branch.contactEmail ?? '',
|
||||
style: pw.TextStyle(font: ttf),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
return pdf.save();
|
||||
@override
|
||||
State<CompanyBranchDialog> createState() => _CompanyBranchDialogState();
|
||||
}
|
||||
|
||||
class _CompanyBranchDialogState extends State<CompanyBranchDialog> {
|
||||
late final CompanyService _companyService;
|
||||
List<Company> _branches = [];
|
||||
bool _isLoading = true;
|
||||
String? _error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_companyService = GetIt.instance<CompanyService>();
|
||||
_loadBranches();
|
||||
}
|
||||
|
||||
// 프린트 버튼 클릭 시 PDF 미리보기 및 인쇄
|
||||
void _printPopupData() async {
|
||||
final pdf = pw.Document();
|
||||
await Printing.layoutPdf(
|
||||
onLayout: (format) async {
|
||||
return _buildPdf(pdf);
|
||||
/// 지점 목록 로드 (SRP - 데이터 로딩 단일 책임)
|
||||
Future<void> _loadBranches() async {
|
||||
try {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
});
|
||||
|
||||
// 전체 회사 목록에서 현재 본사의 지점들 필터링
|
||||
final allCompanies = await ErrorHandler.handleApiCall(
|
||||
() => _companyService.getCompanies(
|
||||
page: 1,
|
||||
perPage: 1000, // 충분히 큰 수로 전체 조회
|
||||
),
|
||||
onError: (failure) => throw failure,
|
||||
);
|
||||
|
||||
if (allCompanies != null) {
|
||||
// parentCompanyId가 현재 본사 ID인 항목들만 필터링
|
||||
_branches = allCompanies.items
|
||||
.where((company) => company.parentCompanyId == widget.mainCompany.id)
|
||||
.toList();
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_error = e.toString();
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 지점 추가 화면 이동
|
||||
void _addBranch() {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
'/company/branch/add',
|
||||
arguments: {
|
||||
'parentCompanyId': widget.mainCompany.id,
|
||||
'parentCompanyName': widget.mainCompany.name,
|
||||
},
|
||||
).then((result) {
|
||||
if (result == true) {
|
||||
_loadBranches(); // 지점 목록 새로고침
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 지점 수정 화면 이동
|
||||
void _editBranch(Company branch) {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
'/company/branch/edit',
|
||||
arguments: {
|
||||
'companyId': branch.id,
|
||||
'parentCompanyId': widget.mainCompany.id,
|
||||
'parentCompanyName': widget.mainCompany.name,
|
||||
},
|
||||
).then((result) {
|
||||
if (result == true) {
|
||||
_loadBranches(); // 지점 목록 새로고침
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 지점 삭제
|
||||
Future<void> _deleteBranch(Company branch) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('지점 삭제'),
|
||||
content: Text('${branch.name} 지점을 삭제하시겠습니까?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('취소'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('삭제'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed == true) {
|
||||
try {
|
||||
await ErrorHandler.handleApiCall(
|
||||
() => _companyService.deleteCompany(branch.id!),
|
||||
onError: (failure) => throw failure,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('${branch.name} 지점이 삭제되었습니다.'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
_loadBranches(); // 지점 목록 새로고침
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('삭제 실패: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 본사 정보 카드 구성
|
||||
Widget _buildHeadquartersCard() {
|
||||
return ShadcnCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
ShadcnBadge(
|
||||
text: '본사',
|
||||
variant: ShadcnBadgeVariant.companyHeadquarters,
|
||||
size: ShadcnBadgeSize.small,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
widget.mainCompany.name,
|
||||
style: ShadcnTheme.headingH5.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildCompanyInfo(widget.mainCompany),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 지점 정보 카드 구성
|
||||
Widget _buildBranchCard(Company branch) {
|
||||
return ShadcnCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
ShadcnBadge(
|
||||
text: '지점',
|
||||
variant: ShadcnBadgeVariant.companyBranch,
|
||||
size: ShadcnBadgeSize.small,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
branch.name,
|
||||
style: ShadcnTheme.headingH5.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit, size: 20),
|
||||
onPressed: () => _editBranch(branch),
|
||||
tooltip: '지점 수정',
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete, size: 20),
|
||||
onPressed: () => _deleteBranch(branch),
|
||||
tooltip: '지점 삭제',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildCompanyInfo(branch),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 정보 공통 구성 (SRP - 정보 표시 단일 책임)
|
||||
Widget _buildCompanyInfo(Company company) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (company.address.toString().isNotEmpty) ...[
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.location_on_outlined,
|
||||
size: 16,
|
||||
color: ShadcnTheme.muted,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
company.address.toString(),
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
if (company.contactName?.isNotEmpty == true) ...[
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.person_outline,
|
||||
size: 16,
|
||||
color: ShadcnTheme.muted,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
company.contactName!,
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
if (company.contactPosition?.isNotEmpty == true) ...[
|
||||
Text(
|
||||
' (${company.contactPosition})',
|
||||
style: ShadcnTheme.bodySmall.copyWith(
|
||||
color: ShadcnTheme.muted,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
if (company.contactPhone?.isNotEmpty == true ||
|
||||
company.contactEmail?.isNotEmpty == true) ...[
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.contact_phone_outlined,
|
||||
size: 16,
|
||||
color: ShadcnTheme.muted,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
company.contactPhone ?? '',
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
if (company.contactEmail?.isNotEmpty == true) ...[
|
||||
Text(
|
||||
' | ${company.contactEmail}',
|
||||
style: ShadcnTheme.bodySmall.copyWith(
|
||||
color: ShadcnTheme.muted,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Branch> branchList = mainCompany.branches ?? [];
|
||||
// 본사와 지점 정보를 한 리스트로 합침
|
||||
final List<Map<String, dynamic>> displayList = [
|
||||
{
|
||||
'type': '본사',
|
||||
'name': mainCompany.name,
|
||||
'companyTypes': mainCompany.companyTypes,
|
||||
'address': mainCompany.address,
|
||||
'contactName': mainCompany.contactName,
|
||||
'contactPosition': mainCompany.contactPosition,
|
||||
'contactPhone': mainCompany.contactPhone,
|
||||
'contactEmail': mainCompany.contactEmail,
|
||||
},
|
||||
...branchList.map(
|
||||
(branch) => {
|
||||
'type': '지점',
|
||||
'name': branch.name,
|
||||
'companyTypes': mainCompany.companyTypes,
|
||||
'address': branch.address,
|
||||
'contactName': branch.contactName,
|
||||
'contactPosition': branch.contactPosition,
|
||||
'contactPhone': branch.contactPhone,
|
||||
'contactEmail': branch.contactEmail,
|
||||
},
|
||||
),
|
||||
];
|
||||
final double maxDialogHeight = MediaQuery.of(context).size.height * 0.7;
|
||||
final double maxDialogWidth = MediaQuery.of(context).size.width * 0.8;
|
||||
final maxDialogHeight = MediaQuery.of(context).size.height * 0.8;
|
||||
final maxDialogWidth = MediaQuery.of(context).size.width * 0.7;
|
||||
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
@@ -240,129 +329,138 @@ class CompanyBranchDialog extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 헤더
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'본사 및 지점 목록',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
Text(
|
||||
'본사 및 지점 관리',
|
||||
style: ShadcnTheme.headingH4.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.print),
|
||||
tooltip: '프린트',
|
||||
onPressed: _printPopupData,
|
||||
ElevatedButton.icon(
|
||||
onPressed: _addBranch,
|
||||
icon: const Icon(Icons.add, size: 16),
|
||||
label: const Text('지점 추가'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ShadcnTheme.primary,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(100, 36),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
tooltip: '닫기',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 콘텐츠
|
||||
Expanded(
|
||||
child: ShadcnCard(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Container(
|
||||
width: maxDialogWidth - 48,
|
||||
constraints: BoxConstraints(minWidth: 900),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: DataTable(
|
||||
columns: const [
|
||||
DataColumn(label: Text('번호')),
|
||||
DataColumn(label: Text('구분')),
|
||||
DataColumn(label: Text('회사명')),
|
||||
DataColumn(label: Text('유형')),
|
||||
DataColumn(label: Text('주소')),
|
||||
DataColumn(label: Text('담당자')),
|
||||
DataColumn(label: Text('직책')),
|
||||
DataColumn(label: Text('전화번호')),
|
||||
DataColumn(label: Text('이메일')),
|
||||
child: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _error != null
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
size: 48,
|
||||
color: ShadcnTheme.destructive,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'데이터 로드 실패',
|
||||
style: ShadcnTheme.headingH5,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_error!,
|
||||
style: ShadcnTheme.bodySmall.copyWith(
|
||||
color: ShadcnTheme.muted,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: _loadBranches,
|
||||
child: const Text('다시 시도'),
|
||||
),
|
||||
],
|
||||
rows:
|
||||
displayList.asMap().entries.map((entry) {
|
||||
final int index = entry.key;
|
||||
final data = entry.value;
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(Text('${index + 1}')),
|
||||
DataCell(Text(data['type'])),
|
||||
DataCell(Text(data['name'])),
|
||||
DataCell(
|
||||
Row(
|
||||
children:
|
||||
(data['companyTypes']
|
||||
as List<CompanyType>)
|
||||
.map(
|
||||
(type) => Container(
|
||||
margin:
|
||||
const EdgeInsets.only(
|
||||
right: 4,
|
||||
),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
type ==
|
||||
CompanyType
|
||||
.customer
|
||||
? Colors
|
||||
.blue
|
||||
.shade50
|
||||
: Colors
|
||||
.green
|
||||
.shade50,
|
||||
borderRadius:
|
||||
BorderRadius.circular(
|
||||
8,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
companyTypeToString(type),
|
||||
style: TextStyle(
|
||||
color:
|
||||
type ==
|
||||
CompanyType
|
||||
.customer
|
||||
? Colors
|
||||
.blue
|
||||
.shade800
|
||||
: Colors
|
||||
.green
|
||||
.shade800,
|
||||
fontWeight:
|
||||
FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
)
|
||||
: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 본사 정보
|
||||
_buildHeadquartersCard(),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 지점 목록
|
||||
if (_branches.isNotEmpty) ...[
|
||||
Text(
|
||||
'지점 목록 (${_branches.length}개)',
|
||||
style: ShadcnTheme.headingH5.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
..._branches.map((branch) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: _buildBranchCard(branch),
|
||||
)),
|
||||
] else ...[
|
||||
Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.muted.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: ShadcnTheme.border,
|
||||
style: BorderStyle.solid,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.domain_outlined,
|
||||
size: 48,
|
||||
color: ShadcnTheme.muted,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'등록된 지점이 없습니다',
|
||||
style: ShadcnTheme.bodyMedium.copyWith(
|
||||
color: ShadcnTheme.muted,
|
||||
),
|
||||
),
|
||||
DataCell(Text(data['address'].toString())),
|
||||
DataCell(Text(data['contactName'] ?? '')),
|
||||
DataCell(
|
||||
Text(data['contactPosition'] ?? ''),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'지점 추가 버튼을 클릭하여 첫 지점을 등록해보세요',
|
||||
style: ShadcnTheme.bodySmall.copyWith(
|
||||
color: ShadcnTheme.muted,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
DataCell(Text(data['contactPhone'] ?? '')),
|
||||
DataCell(Text(data['contactEmail'] ?? '')),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -370,4 +468,4 @@ class CompanyBranchDialog extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user