refactor: 코드베이스 정리 및 에러 처리 개선

- API 클라이언트 및 인증 인터셉터 에러 처리 강화
- 의존성 주입 실패 시에도 앱 실행 가능하도록 개선
- 사용하지 않는 레거시 UI 컴포넌트 및 화면 제거
- pubspec.yaml 의존성 업데이트

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-07-25 18:15:21 +09:00
parent 71b7b7f40b
commit ad2c699ff7
39 changed files with 193 additions and 4134 deletions

View File

@@ -1,80 +0,0 @@
import 'package:flutter/material.dart';
import 'package:superport/screens/sidebar/sidebar_screen.dart';
import 'package:superport/screens/overview/overview_screen.dart';
import 'package:superport/screens/equipment/equipment_list.dart';
import 'package:superport/screens/company/company_list.dart';
import 'package:superport/screens/user/user_list.dart';
import 'package:superport/screens/license/license_list.dart';
import 'package:superport/screens/warehouse_location/warehouse_location_list.dart';
import 'package:superport/utils/constants.dart';
/// SPA 스타일의 앱 레이아웃 클래스
/// 사이드바는 고정되고 내용만 변경되는 구조를 제공
class AppLayout extends StatefulWidget {
final String initialRoute;
const AppLayout({Key? key, this.initialRoute = Routes.home})
: super(key: key);
@override
_AppLayoutState createState() => _AppLayoutState();
}
class _AppLayoutState extends State<AppLayout> {
late String _currentRoute;
@override
void initState() {
super.initState();
_currentRoute = widget.initialRoute;
}
/// 현재 경로에 따라 적절한 컨텐츠 섹션을 반환
Widget _getContentForRoute(String route) {
switch (route) {
case Routes.home:
return const OverviewScreen();
case Routes.equipment:
case Routes.equipmentInList:
case Routes.equipmentOutList:
case Routes.equipmentRentList:
// 장비 목록 화면에 현재 라우트 정보를 전달
return EquipmentListScreen(currentRoute: route);
case Routes.company:
return const CompanyListScreen();
case Routes.license:
return const MaintenanceListScreen();
case Routes.warehouseLocation:
return const WarehouseLocationListScreen();
default:
return const OverviewScreen();
}
}
/// 경로 변경 메서드
void _navigateTo(String route) {
setState(() {
_currentRoute = route;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
// 왼쪽 사이드바
SizedBox(
width: 280,
child: SidebarMenu(
currentRoute: _currentRoute,
onRouteChanged: _navigateTo,
),
),
// 오른쪽 컨텐츠 영역
Expanded(child: _getContentForRoute(_currentRoute)),
],
),
);
}
}

View File

@@ -1,5 +1,3 @@
export 'custom_widgets/page_title.dart';
export 'custom_widgets/data_table_card.dart';
export 'custom_widgets/form_field_wrapper.dart';
export 'custom_widgets/date_picker_field.dart';
export 'custom_widgets/highlight_text.dart';

View File

@@ -1,32 +0,0 @@
import 'package:flutter/material.dart';
import 'package:superport/screens/common/theme_tailwind.dart';
// 데이터 테이블 카드
class DataTableCard extends StatelessWidget {
final Widget child;
final String? title;
final double? width;
const DataTableCard({Key? key, required this.child, this.title, this.width})
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: width,
decoration: AppThemeTailwind.cardDecoration,
margin: const EdgeInsets.only(bottom: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (title != null)
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(title!, style: AppThemeTailwind.subheadingStyle),
),
Padding(padding: const EdgeInsets.all(16.0), child: child),
],
),
);
}
}

View File

@@ -1,27 +0,0 @@
import 'package:flutter/material.dart';
import 'package:superport/screens/common/theme_tailwind.dart';
// 페이지 타이틀 위젯
class PageTitle extends StatelessWidget {
final String title;
final Widget? rightWidget;
final double? width;
const PageTitle({Key? key, required this.title, this.rightWidget, this.width})
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: width,
margin: const EdgeInsets.only(bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title, style: AppThemeTailwind.headingStyle),
if (rightWidget != null) rightWidget!,
],
),
);
}
}

View File

@@ -1,9 +0,0 @@
/// 메트로닉 스타일 공통 레이아웃 컴포넌트 barrel 파일
/// 각 위젯은 SRP에 따라 별도 파일로 분리되어 있습니다.
export 'metronic_page_container.dart';
export 'metronic_card.dart';
export 'metronic_stats_card.dart';
export 'metronic_page_title.dart';
export 'metronic_data_table.dart';
export 'metronic_form_field.dart';
export 'metronic_tab_container.dart';

View File

@@ -1,126 +0,0 @@
import 'package:flutter/material.dart';
import 'package:superport/screens/common/theme_tailwind.dart';
class MainLayout extends StatelessWidget {
final String title;
final Widget child;
final String currentRoute;
final List<Widget>? actions;
final bool showBackButton;
final Widget? floatingActionButton;
const MainLayout({
Key? key,
required this.title,
required this.child,
required this.currentRoute,
this.actions,
this.showBackButton = false,
this.floatingActionButton,
}) : super(key: key);
@override
Widget build(BuildContext context) {
// MetronicCloud 스타일: 상단부 플랫, 여백 넓게, 타이틀/경로/버튼 스타일링
return Scaffold(
backgroundColor: AppThemeTailwind.surface,
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 상단 앱바
_buildAppBar(context),
// 컨텐츠
Expanded(child: child),
],
),
floatingActionButton: floatingActionButton,
);
}
Widget _buildAppBar(BuildContext context) {
// 상단 앱바: 경로 텍스트가 수직 중앙에 오도록 조정, 배경색/글자색 변경
return Container(
height: 88,
padding: const EdgeInsets.symmetric(horizontal: 40),
decoration: BoxDecoration(
color: AppThemeTailwind.surface, // 회색 배경
border: const Border(
bottom: BorderSide(color: Color(0xFFF3F6F9), width: 1),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center, // Row 내에서 수직 중앙 정렬
children: [
// 경로 및 타이틀 영역 (수직 중앙 정렬)
Column(
mainAxisAlignment: MainAxisAlignment.center, // Column 내에서 수직 중앙 정렬
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 경로 텍스트 (폰트 사이즈 2배, 검은색 글자)
Text(
_getBreadcrumb(currentRoute),
style: TextStyle(
fontSize: 26,
color: AppThemeTailwind.dark,
), // 검은색 글자
),
// 타이틀이 있을 때만 표시
if (title.isNotEmpty)
Text(
title,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: AppThemeTailwind.dark,
),
),
],
),
const Spacer(),
if (actions != null)
Row(
children:
actions!
.map(
(w) => Padding(
padding: const EdgeInsets.only(left: 8),
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: AppThemeTailwind.muted,
width: 1,
),
color: const Color(0xFFF7F8FA),
borderRadius: BorderRadius.circular(8),
),
child: w,
),
),
)
.toList(),
),
],
),
);
}
// 현재 라우트에 따라 경로 문자열을 반환하는 함수
String _getBreadcrumb(String route) {
// 실제 라우트에 따라 경로를 한글로 변환 (예시)
switch (route) {
case '/':
case '/home':
return '홈 / 대시보드';
case '/equipment':
return '홈 / 장비 관리';
case '/company':
return '홈 / 회사 관리';
case '/maintenance':
return '홈 / 유지보수 관리';
case '/item':
return '홈 / 물품 관리';
default:
return '';
}
}
}

View File

@@ -1,46 +0,0 @@
import 'package:flutter/material.dart';
import 'package:superport/screens/common/theme_tailwind.dart';
/// 메트로닉 스타일 카드 위젯 (SRP 분리)
class MetronicCard extends StatelessWidget {
final String? title;
final Widget child;
final List<Widget>? actions;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? margin;
const MetronicCard({
Key? key,
this.title,
required this.child,
this.actions,
this.padding,
this.margin,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: AppThemeTailwind.cardDecoration,
margin: margin,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (title != null || actions != null)
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (title != null)
Text(title!, style: AppThemeTailwind.subheadingStyle),
if (actions != null) Row(children: actions!),
],
),
),
Padding(padding: padding ?? const EdgeInsets.all(16), child: child),
],
),
);
}
}

View File

@@ -1,57 +0,0 @@
import 'package:flutter/material.dart';
import 'package:superport/screens/common/theme_tailwind.dart';
import 'package:superport/screens/common/metronic_card.dart';
/// 메트로닉 스타일 데이터 테이블 카드 위젯 (SRP 분리)
class MetronicDataTable extends StatelessWidget {
final List<DataColumn> columns;
final List<DataRow> rows;
final String? title;
final bool isLoading;
final String? emptyMessage;
const MetronicDataTable({
Key? key,
required this.columns,
required this.rows,
this.title,
this.isLoading = false,
this.emptyMessage,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return MetronicCard(
title: title,
child:
isLoading
? const Center(child: CircularProgressIndicator())
: rows.isEmpty
? Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Text(
emptyMessage ?? '데이터가 없습니다.',
style: AppThemeTailwind.bodyStyle,
),
),
)
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
columns: columns,
rows: rows,
headingRowColor: MaterialStateProperty.all(
AppThemeTailwind.light,
),
dataRowMaxHeight: 60,
columnSpacing: 24,
horizontalMargin: 16,
),
),
),
);
}
}

View File

@@ -1,57 +0,0 @@
import 'package:flutter/material.dart';
import 'package:superport/screens/common/theme_tailwind.dart';
/// 메트로닉 스타일 폼 필드 래퍼 위젯 (SRP 분리)
class MetronicFormField extends StatelessWidget {
final String label;
final Widget child;
final bool isRequired;
final String? helperText;
const MetronicFormField({
Key? key,
required this.label,
required this.child,
this.isRequired = false,
this.helperText,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
label,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
color: AppThemeTailwind.dark,
),
),
if (isRequired)
const Text(
' *',
style: TextStyle(
color: AppThemeTailwind.danger,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 8),
child,
if (helperText != null)
Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Text(helperText!, style: AppThemeTailwind.smallText),
),
],
),
);
}
}

View File

@@ -1,35 +0,0 @@
import 'package:flutter/material.dart';
import 'package:superport/screens/common/theme_tailwind.dart';
/// 메트로닉 스타일 페이지 컨테이너 위젯 (SRP 분리)
class MetronicPageContainer extends StatelessWidget {
final String title;
final Widget child;
final List<Widget>? actions;
final bool showBackButton;
const MetronicPageContainer({
Key? key,
required this.title,
required this.child,
this.actions,
this.showBackButton = true,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
automaticallyImplyLeading: showBackButton,
actions: actions,
elevation: 0,
),
body: Container(
color: AppThemeTailwind.surface,
padding: const EdgeInsets.all(16),
child: child,
),
);
}
}

View File

@@ -1,36 +0,0 @@
import 'package:flutter/material.dart';
import 'package:superport/screens/common/theme_tailwind.dart';
/// 메트로닉 스타일 페이지 타이틀 위젯 (SRP 분리)
class MetronicPageTitle extends StatelessWidget {
final String title;
final VoidCallback? onAddPressed;
final String? addButtonLabel;
const MetronicPageTitle({
Key? key,
required this.title,
this.onAddPressed,
this.addButtonLabel,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title, style: AppThemeTailwind.headingStyle),
if (onAddPressed != null)
ElevatedButton.icon(
onPressed: onAddPressed,
icon: const Icon(Icons.add),
label: Text(addButtonLabel ?? '추가'),
style: AppThemeTailwind.primaryButtonStyle,
),
],
),
);
}
}

View File

@@ -1,105 +0,0 @@
import 'package:flutter/material.dart';
import 'package:superport/screens/common/theme_tailwind.dart';
/// 메트로닉 스타일 통계 카드 위젯 (SRP 분리)
class MetronicStatsCard extends StatelessWidget {
final String title;
final String value;
final String? subtitle;
final IconData? icon;
final Color? iconBackgroundColor;
final bool showTrend;
final double? trendPercentage;
final bool isPositiveTrend;
const MetronicStatsCard({
Key? key,
required this.title,
required this.value,
this.subtitle,
this.icon,
this.iconBackgroundColor,
this.showTrend = false,
this.trendPercentage,
this.isPositiveTrend = true,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: AppThemeTailwind.cardDecoration,
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: AppThemeTailwind.bodyStyle.copyWith(
color: AppThemeTailwind.muted,
fontSize: 12,
),
),
if (icon != null)
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: iconBackgroundColor ?? AppThemeTailwind.light,
shape: BoxShape.circle,
),
child: Icon(
icon,
color:
iconBackgroundColor != null
? Colors.white
: AppThemeTailwind.primary,
size: 16,
),
),
],
),
const SizedBox(height: 8),
Text(
value,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: AppThemeTailwind.dark,
),
),
if (subtitle != null || showTrend) const SizedBox(height: 4),
if (subtitle != null)
Text(subtitle!, style: AppThemeTailwind.smallText),
if (showTrend && trendPercentage != null)
Row(
children: [
Icon(
isPositiveTrend ? Icons.arrow_upward : Icons.arrow_downward,
color:
isPositiveTrend
? AppThemeTailwind.success
: AppThemeTailwind.danger,
size: 12,
),
const SizedBox(width: 4),
Text(
'${trendPercentage!.toStringAsFixed(1)}%',
style: TextStyle(
color:
isPositiveTrend
? AppThemeTailwind.success
: AppThemeTailwind.danger,
fontWeight: FontWeight.w600,
fontSize: 12,
),
),
],
),
],
),
);
}
}

View File

@@ -1,48 +0,0 @@
import 'package:flutter/material.dart';
import 'package:superport/screens/common/theme_tailwind.dart';
/// 메트로닉 스타일 탭 컨테이너 위젯 (SRP 분리)
class MetronicTabContainer extends StatelessWidget {
final List<String> tabs;
final List<Widget> tabViews;
final int initialIndex;
const MetronicTabContainer({
Key? key,
required this.tabs,
required this.tabViews,
this.initialIndex = 0,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: tabs.length,
initialIndex: initialIndex,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: Color(0xFFE5E7EB), width: 1),
),
),
child: TabBar(
tabs: tabs.map((tab) => Tab(text: tab)).toList(),
labelColor: AppThemeTailwind.primary,
unselectedLabelColor: AppThemeTailwind.muted,
labelStyle: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
),
indicatorColor: AppThemeTailwind.primary,
indicatorWeight: 2,
),
),
Expanded(child: TabBarView(children: tabViews)),
],
),
);
}
}