clean: Phase 1 - 26개 미사용 파일 삭제 완료

삭제된 파일:
- 구식 Equipment 모델: 18개 파일 (6개 모델 × 3개씩)
- 미사용 UseCase 컨테이너: 5개 파일
- 레거시 UI 컴포넌트: 5개 파일
- Import 오류 수정: custom_widgets.dart 정리

총 28개 변경사항 (삭제 26개 + 수정 1개 + 기타)
Flutter analyze: ERROR 0개 
Build test: 성공 

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-09-02 19:55:00 +09:00
parent c419f8f458
commit 2c20999025
29 changed files with 0 additions and 4851 deletions

View File

@@ -1,6 +1 @@
export 'custom_widgets/form_field_wrapper.dart';
export 'custom_widgets/date_picker_field.dart';
export 'custom_widgets/highlight_text.dart';
export 'custom_widgets/autocomplete_dropdown.dart';
export 'custom_widgets/category_selection_field.dart';
export 'custom_widgets/category_data.dart';

View File

@@ -1,88 +0,0 @@
import 'package:flutter/material.dart';
import 'highlight_text.dart';
// 자동완성 드롭다운 공통 위젯
class AutocompleteDropdown extends StatelessWidget {
// 드롭다운에 표시할 항목 리스트
final List<String> items;
// 현재 입력된 텍스트(하이라이트 기준)
final String inputText;
// 항목 선택 시 콜백
final void Function(String) onSelect;
// 드롭다운 표시 여부
final bool showDropdown;
// 최대 높이(항목 개수에 따라 자동 조절)
final double maxHeight;
// 드롭다운이 비었을 때 표시할 위젯
final Widget emptyWidget;
const AutocompleteDropdown({
super.key,
required this.items,
required this.inputText,
required this.onSelect,
required this.showDropdown,
this.maxHeight = 200,
this.emptyWidget = const Padding(
padding: EdgeInsets.all(12.0),
child: Text('검색 결과가 없습니다'),
),
});
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
height:
showDropdown
? (items.length > 4 ? maxHeight : items.length * 50.0)
: 0,
margin: EdgeInsets.only(top: showDropdown ? 4 : 0),
child: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: GestureDetector(
onTap: () {}, // 이벤트 버블링 방지
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4),
border: Border.all(color: Colors.grey.shade300),
boxShadow: [
BoxShadow(
color: Colors.grey.withAlpha(77),
spreadRadius: 1,
blurRadius: 3,
offset: const Offset(0, 1),
),
],
),
child:
items.isEmpty
? emptyWidget
: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: items.length,
separatorBuilder:
(context, index) =>
Divider(height: 1, color: Colors.grey.shade200),
itemBuilder: (context, index) {
final String item = items[index];
return ListTile(
dense: true,
title: HighlightText(
text: item,
highlight: inputText,
highlightColor: Theme.of(context).primaryColor,
),
onTap: () => onSelect(item),
);
},
),
),
),
),
);
}
}

View File

@@ -1,18 +0,0 @@
// 카테고리 데이터 (예시)
final Map<String, Map<String, List<String>>> categoryData = {
'컴퓨터': {
'데스크탑': ['사무용', '게이밍', '워크스테이션'],
'노트북': ['사무용', '게이밍', '울트라북'],
'태블릿': ['안드로이드', 'iOS', '윈도우'],
},
'네트워크': {
'라우터': ['가정용', '기업용', '산업용'],
'스위치': ['관리형', '비관리형'],
'액세스 포인트': ['실내용', '실외용'],
},
'주변기기': {
'모니터': ['LCD', 'LED', 'OLED'],
'키보드': ['유선', '무선', '기계식'],
'마우스': ['유선', '무선', '트랙볼'],
},
};

View File

@@ -1,562 +0,0 @@
import 'package:flutter/material.dart';
import 'autocomplete_dropdown.dart';
import 'form_field_wrapper.dart';
import 'category_data.dart';
// 카테고리 선택 필드 (대분류/중분류/소분류)
class CategorySelectionField extends StatefulWidget {
final String category;
final String subCategory;
final String subSubCategory;
final Function(String, String, String) onCategoryChanged;
final bool isRequired;
const CategorySelectionField({
super.key,
required this.category,
required this.subCategory,
required this.subSubCategory,
required this.onCategoryChanged,
this.isRequired = false,
});
@override
State<CategorySelectionField> createState() => _CategorySelectionFieldState();
}
class _CategorySelectionFieldState extends State<CategorySelectionField> {
// 검색 관련 컨트롤러 및 상태 변수
final TextEditingController _categoryController = TextEditingController();
final FocusNode _categoryFocusNode = FocusNode();
bool _showCategoryDropdown = false;
List<String> _filteredCategories = [];
// 중분류 관련 변수
final TextEditingController _subCategoryController = TextEditingController();
final FocusNode _subCategoryFocusNode = FocusNode();
bool _showSubCategoryDropdown = false;
List<String> _filteredSubCategories = [];
// 소분류 관련 변수
final TextEditingController _subSubCategoryController =
TextEditingController();
final FocusNode _subSubCategoryFocusNode = FocusNode();
bool _showSubSubCategoryDropdown = false;
List<String> _filteredSubSubCategories = [];
List<String> _allCategories = [];
String _selectedCategory = '';
String _selectedSubCategory = '';
String _selectedSubSubCategory = '';
@override
void initState() {
super.initState();
_selectedCategory = widget.category;
_selectedSubCategory = widget.subCategory;
_selectedSubSubCategory = widget.subSubCategory;
_categoryController.text = _selectedCategory;
_subCategoryController.text = _selectedSubCategory;
_subSubCategoryController.text = _selectedSubSubCategory;
// 모든 카테고리 목록 초기화
_allCategories = categoryData.keys.toList();
_filteredCategories = List.from(_allCategories);
// 중분류 목록 초기화
_updateSubCategories();
// 소분류 목록 초기화
_updateSubSubCategories();
// 대분류 컨트롤러 리스너 설정
_categoryController.addListener(_onCategoryTextChanged);
_categoryFocusNode.addListener(() {
setState(() {
if (_categoryFocusNode.hasFocus) {
_showCategoryDropdown = _filteredCategories.isNotEmpty;
} else {
_showCategoryDropdown = false;
}
});
});
// 중분류 컨트롤러 리스너 설정
_subCategoryController.addListener(_onSubCategoryTextChanged);
_subCategoryFocusNode.addListener(() {
setState(() {
if (_subCategoryFocusNode.hasFocus) {
_showSubCategoryDropdown = _filteredSubCategories.isNotEmpty;
} else {
_showSubCategoryDropdown = false;
}
});
});
// 소분류 컨트롤러 리스너 설정
_subSubCategoryController.addListener(_onSubSubCategoryTextChanged);
_subSubCategoryFocusNode.addListener(() {
setState(() {
if (_subSubCategoryFocusNode.hasFocus) {
_showSubSubCategoryDropdown = _filteredSubSubCategories.isNotEmpty;
} else {
_showSubSubCategoryDropdown = false;
}
});
});
}
@override
void dispose() {
_categoryController.dispose();
_categoryFocusNode.dispose();
_subCategoryController.dispose();
_subCategoryFocusNode.dispose();
_subSubCategoryController.dispose();
_subSubCategoryFocusNode.dispose();
super.dispose();
}
// 중분류 목록 업데이트
void _updateSubCategories() {
if (_selectedCategory.isNotEmpty) {
final subCategories =
categoryData[_selectedCategory]?.keys.toList() ?? [];
_filteredSubCategories = List.from(subCategories);
} else {
_filteredSubCategories = [];
}
}
// 소분류 목록 업데이트
void _updateSubSubCategories() {
if (_selectedCategory.isNotEmpty && _selectedSubCategory.isNotEmpty) {
final subSubCategories =
categoryData[_selectedCategory]?[_selectedSubCategory] ?? [];
_filteredSubSubCategories = List.from(subSubCategories);
} else {
_filteredSubSubCategories = [];
}
}
void _onCategoryTextChanged() {
final text = _categoryController.text;
setState(() {
_selectedCategory = text;
if (text.isEmpty) {
_filteredCategories = List.from(_allCategories);
} else {
_filteredCategories =
_allCategories
.where(
(item) => item.toLowerCase().contains(text.toLowerCase()),
)
.toList();
// 시작 부분이 일치하는 항목 우선 정렬
_filteredCategories.sort((a, b) {
bool aStartsWith = a.toLowerCase().startsWith(text.toLowerCase());
bool bStartsWith = b.toLowerCase().startsWith(text.toLowerCase());
if (aStartsWith && !bStartsWith) return -1;
if (!aStartsWith && bStartsWith) return 1;
return a.compareTo(b);
});
}
_showCategoryDropdown =
_filteredCategories.isNotEmpty && _categoryFocusNode.hasFocus;
// 카테고리가 변경되면 하위 카테고리 초기화
if (_selectedCategory != widget.category) {
_selectedSubCategory = '';
_subCategoryController.text = '';
_selectedSubSubCategory = '';
_subSubCategoryController.text = '';
_updateSubCategories();
_updateSubSubCategories();
}
// 콜백 호출
widget.onCategoryChanged(
_selectedCategory,
_selectedSubCategory,
_selectedSubSubCategory,
);
});
}
// 중분류 텍스트 변경 핸들러
void _onSubCategoryTextChanged() {
final text = _subCategoryController.text;
setState(() {
_selectedSubCategory = text;
if (_selectedCategory.isNotEmpty) {
final subCategories =
categoryData[_selectedCategory]?.keys.toList() ?? [];
if (text.isEmpty) {
_filteredSubCategories = List.from(subCategories);
} else {
_filteredSubCategories =
subCategories
.where(
(item) => item.toLowerCase().contains(text.toLowerCase()),
)
.toList();
// 시작 부분이 일치하는 항목 우선 정렬
_filteredSubCategories.sort((a, b) {
bool aStartsWith = a.toLowerCase().startsWith(text.toLowerCase());
bool bStartsWith = b.toLowerCase().startsWith(text.toLowerCase());
if (aStartsWith && !bStartsWith) return -1;
if (!aStartsWith && bStartsWith) return 1;
return a.compareTo(b);
});
}
} else {
_filteredSubCategories = [];
}
_showSubCategoryDropdown =
_filteredSubCategories.isNotEmpty && _subCategoryFocusNode.hasFocus;
// 중분류가 변경되면 소분류 초기화
if (_selectedSubCategory != widget.subCategory) {
_selectedSubSubCategory = '';
_subSubCategoryController.text = '';
_updateSubSubCategories();
}
// 콜백 호출
widget.onCategoryChanged(
_selectedCategory,
_selectedSubCategory,
_selectedSubSubCategory,
);
});
}
// 소분류 텍스트 변경 핸들러
void _onSubSubCategoryTextChanged() {
final text = _subSubCategoryController.text;
setState(() {
_selectedSubSubCategory = text;
if (_selectedCategory.isNotEmpty && _selectedSubCategory.isNotEmpty) {
final subSubCategories =
categoryData[_selectedCategory]?[_selectedSubCategory] ?? [];
if (text.isEmpty) {
_filteredSubSubCategories = List.from(subSubCategories);
} else {
_filteredSubSubCategories =
subSubCategories
.where(
(item) => item.toLowerCase().contains(text.toLowerCase()),
)
.toList();
// 시작 부분이 일치하는 항목 우선 정렬
_filteredSubSubCategories.sort((a, b) {
bool aStartsWith = a.toLowerCase().startsWith(text.toLowerCase());
bool bStartsWith = b.toLowerCase().startsWith(text.toLowerCase());
if (aStartsWith && !bStartsWith) return -1;
if (!aStartsWith && bStartsWith) return 1;
return a.compareTo(b);
});
}
} else {
_filteredSubSubCategories = [];
}
_showSubSubCategoryDropdown =
_filteredSubSubCategories.isNotEmpty &&
_subSubCategoryFocusNode.hasFocus;
// 콜백 호출
widget.onCategoryChanged(
_selectedCategory,
_selectedSubCategory,
_selectedSubSubCategory,
);
});
}
void _selectCategory(String category) {
setState(() {
_selectedCategory = category;
_categoryController.text = category;
_showCategoryDropdown = false;
_selectedSubCategory = '';
_subCategoryController.text = '';
_selectedSubSubCategory = '';
_subSubCategoryController.text = '';
_updateSubCategories();
_updateSubSubCategories();
widget.onCategoryChanged(
_selectedCategory,
_selectedSubCategory,
_selectedSubSubCategory,
);
});
}
// 중분류 선택 핸들러
void _selectSubCategory(String subCategory) {
setState(() {
_selectedSubCategory = subCategory;
_subCategoryController.text = subCategory;
_showSubCategoryDropdown = false;
_selectedSubSubCategory = '';
_subSubCategoryController.text = '';
_updateSubSubCategories();
widget.onCategoryChanged(
_selectedCategory,
_selectedSubCategory,
_selectedSubSubCategory,
);
});
}
// 소분류 선택 핸들러
void _selectSubSubCategory(String subSubCategory) {
setState(() {
_selectedSubSubCategory = subSubCategory;
_subSubCategoryController.text = subSubCategory;
_showSubSubCategoryDropdown = false;
widget.onCategoryChanged(
_selectedCategory,
_selectedSubCategory,
_selectedSubSubCategory,
);
});
}
@override
Widget build(BuildContext context) {
return FormFieldWrapper(
label: '카테고리',
isRequired: widget.isRequired,
child: Column(
children: [
// 대분류 입력 필드 (자동완성)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: _categoryController,
focusNode: _categoryFocusNode,
decoration: InputDecoration(
hintText: '대분류',
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,
),
suffixIcon:
_categoryController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
setState(() {
_categoryController.clear();
_selectedCategory = '';
_selectedSubCategory = '';
_selectedSubSubCategory = '';
_subCategoryController.clear();
_subSubCategoryController.clear();
_filteredCategories = List.from(_allCategories);
_filteredSubCategories = [];
_filteredSubSubCategories = [];
_showCategoryDropdown =
_categoryFocusNode.hasFocus;
widget.onCategoryChanged('', '', '');
});
},
)
: IconButton(
icon: const Icon(Icons.arrow_drop_down),
onPressed: () {
setState(() {
_showCategoryDropdown = !_showCategoryDropdown;
});
},
),
),
validator: (value) {
if (widget.isRequired && (value == null || value.isEmpty)) {
return '대분류를 선택해주세요';
}
return null;
},
onTap: () {
setState(() {
if (!_showCategoryDropdown) {
_showCategoryDropdown = true;
}
});
},
),
// 대분류 자동완성 드롭다운
AutocompleteDropdown(
items: _filteredCategories,
inputText: _categoryController.text,
onSelect: _selectCategory,
showDropdown: _showCategoryDropdown,
),
],
),
const SizedBox(height: 12),
// 중분류 및 소분류 선택 행
Row(
children: [
// 중분류 입력 필드 (자동완성)
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: _subCategoryController,
focusNode: _subCategoryFocusNode,
decoration: InputDecoration(
hintText: '중분류',
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,
),
suffixIcon:
_subCategoryController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
setState(() {
_subCategoryController.clear();
_selectedSubCategory = '';
_selectedSubSubCategory = '';
_subSubCategoryController.clear();
_updateSubCategories();
_updateSubSubCategories();
_showSubCategoryDropdown =
_subCategoryFocusNode.hasFocus;
widget.onCategoryChanged(
_selectedCategory,
'',
'',
);
});
},
)
: IconButton(
icon: const Icon(Icons.arrow_drop_down),
onPressed: () {
setState(() {
_showSubCategoryDropdown =
!_showSubCategoryDropdown;
});
},
),
),
enabled: _selectedCategory.isNotEmpty,
onTap: () {
setState(() {
if (!_showSubCategoryDropdown &&
_filteredSubCategories.isNotEmpty) {
_showSubCategoryDropdown = true;
}
});
},
),
// 중분류 자동완성 드롭다운
AutocompleteDropdown(
items: _filteredSubCategories,
inputText: _subCategoryController.text,
onSelect: _selectSubCategory,
showDropdown: _showSubCategoryDropdown,
),
],
),
),
const SizedBox(width: 12),
// 소분류 입력 필드 (자동완성)
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: _subSubCategoryController,
focusNode: _subSubCategoryFocusNode,
decoration: InputDecoration(
hintText: '소분류',
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,
),
suffixIcon:
_subSubCategoryController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
setState(() {
_subSubCategoryController.clear();
_selectedSubSubCategory = '';
_updateSubSubCategories();
_showSubSubCategoryDropdown =
_subSubCategoryFocusNode.hasFocus;
widget.onCategoryChanged(
_selectedCategory,
_selectedSubCategory,
'',
);
});
},
)
: IconButton(
icon: const Icon(Icons.arrow_drop_down),
onPressed: () {
setState(() {
_showSubSubCategoryDropdown =
!_showSubSubCategoryDropdown;
});
},
),
),
enabled:
_selectedCategory.isNotEmpty &&
_selectedSubCategory.isNotEmpty,
onTap: () {
setState(() {
if (!_showSubSubCategoryDropdown &&
_filteredSubSubCategories.isNotEmpty) {
_showSubSubCategoryDropdown = true;
}
});
},
),
// 소분류 자동완성 드롭다운
AutocompleteDropdown(
items: _filteredSubSubCategories,
inputText: _subSubCategoryController.text,
onSelect: _selectSubSubCategory,
showDropdown: _showSubSubCategoryDropdown,
),
],
),
),
],
),
],
),
);
}
}

View File

@@ -1,57 +0,0 @@
import 'package:flutter/material.dart';
import 'form_field_wrapper.dart';
import 'package:superport/screens/common/theme_shadcn.dart';
// 날짜 선택 필드
class DatePickerField extends StatelessWidget {
final DateTime selectedDate;
final Function(DateTime) onDateChanged;
final bool allowFutureDate;
final bool isRequired;
const DatePickerField({
super.key,
required this.selectedDate,
required this.onDateChanged,
this.allowFutureDate = false,
this.isRequired = false,
});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(2000),
lastDate: allowFutureDate ? DateTime(2100) : DateTime.now(),
);
if (picked != null && picked != selectedDate) {
onDateChanged(picked);
}
},
child: FormFieldWrapper(
label: '날짜',
isRequired: isRequired,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 15),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade400),
borderRadius: BorderRadius.circular(4),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${selectedDate.year}-${selectedDate.month.toString().padLeft(2, '0')}-${selectedDate.day.toString().padLeft(2, '0')}',
style: ShadcnTheme.bodyMedium,
),
const Icon(Icons.calendar_today, size: 20),
],
),
),
),
);
}
}

View File

@@ -1,53 +0,0 @@
import 'package:flutter/material.dart';
// 자동완성 드롭다운에서 텍스트 하이라이트를 위한 위젯
class HighlightText extends StatelessWidget {
// 전체 텍스트
final String text;
// 하이라이트할 부분
final String highlight;
// 하이라이트 색상
final Color highlightColor;
// 텍스트 스타일
final TextStyle? style;
const HighlightText({
super.key,
required this.text,
required this.highlight,
this.highlightColor = Colors.blue,
this.style,
});
@override
Widget build(BuildContext context) {
if (highlight.isEmpty) {
// 하이라이트가 없으면 전체 텍스트 반환
return Text(text, style: style);
}
final String lowerText = text.toLowerCase();
final String lowerHighlight = highlight.toLowerCase();
final int start = lowerText.indexOf(lowerHighlight);
if (start < 0) {
// 일치하는 부분이 없으면 전체 텍스트 반환
return Text(text, style: style);
}
final int end = start + highlight.length;
return RichText(
text: TextSpan(
style: style ?? DefaultTextStyle.of(context).style,
children: [
if (start > 0) TextSpan(text: text.substring(0, start)),
TextSpan(
text: text.substring(start, end),
style: TextStyle(
fontWeight: FontWeight.bold,
color: highlightColor,
),
),
if (end < text.length) TextSpan(text: text.substring(end)),
],
),
);
}
}