fix(inventory-ui): 필터·창고 선택 상호작용 정비
- 입고/출고/대여 상태·대여구분 드롭다운에 제네릭 타입을 명시해 null 허용 옵션에서도 SDK 경고를 제거\n- 입고 목록 include 라벨에 customers→'고객 포함'을 추가해 UI 설명을 통일\n- WarehouseSelectField가 선택된 라벨과 동일한 검색어일 때 전체 옵션을 복원하고 suggestions를 초기화하도록 로직을 보강\n- FilterBar의 필터 적용 배지를 윤곽 스타일로 교체하고 테마 색상 대비를 조정\n- DatePicker 버튼을 outline 버튼 자체에서 정렬/아이콘 슬롯으로 구성해 긴 날짜 라벨이 잘리지 않도록 개선\n- 필터 배지/날짜 버튼 UI 변경에 맞춰 인벤토리 요약 골든 이미지를 갱신\n- flutter analyze, flutter test로 회귀를 검증
This commit is contained in:
@@ -396,12 +396,12 @@ class _InboundPageState extends State<InboundPage> {
|
|||||||
setState(() => _pendingStatus = value);
|
setState(() => _pendingStatus = value);
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
const ShadOption(
|
ShadOption<String?>(
|
||||||
value: null,
|
value: null,
|
||||||
child: Text(InboundTableSpec.allStatusLabel),
|
child: Text(InboundTableSpec.allStatusLabel),
|
||||||
),
|
),
|
||||||
for (final option in _statusOptions)
|
for (final option in _statusOptions)
|
||||||
ShadOption(value: option, child: Text(option)),
|
ShadOption<String?>(value: option, child: Text(option)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ class InboundTableSpec {
|
|||||||
switch (value) {
|
switch (value) {
|
||||||
case 'lines':
|
case 'lines':
|
||||||
return '상세정보 포함';
|
return '상세정보 포함';
|
||||||
|
case 'customers':
|
||||||
|
return '고객 포함';
|
||||||
default:
|
default:
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -481,12 +481,12 @@ class _OutboundPageState extends State<OutboundPage> {
|
|||||||
Text(value ?? OutboundTableSpec.allStatusLabel),
|
Text(value ?? OutboundTableSpec.allStatusLabel),
|
||||||
onChanged: (value) => setState(() => _pendingStatus = value),
|
onChanged: (value) => setState(() => _pendingStatus = value),
|
||||||
options: [
|
options: [
|
||||||
const ShadOption(
|
ShadOption<String?>(
|
||||||
value: null,
|
value: null,
|
||||||
child: Text(OutboundTableSpec.allStatusLabel),
|
child: Text(OutboundTableSpec.allStatusLabel),
|
||||||
),
|
),
|
||||||
for (final option in _statusOptions)
|
for (final option in _statusOptions)
|
||||||
ShadOption(value: option, child: Text(option)),
|
ShadOption<String?>(value: option, child: Text(option)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -438,12 +438,12 @@ class _RentalPageState extends State<RentalPage> {
|
|||||||
Text(value ?? RentalTableSpec.allStatusLabel),
|
Text(value ?? RentalTableSpec.allStatusLabel),
|
||||||
onChanged: (value) => setState(() => _pendingStatus = value),
|
onChanged: (value) => setState(() => _pendingStatus = value),
|
||||||
options: [
|
options: [
|
||||||
const ShadOption(
|
ShadOption<String?>(
|
||||||
value: null,
|
value: null,
|
||||||
child: Text(RentalTableSpec.allStatusLabel),
|
child: Text(RentalTableSpec.allStatusLabel),
|
||||||
),
|
),
|
||||||
for (final option in _statusOptions)
|
for (final option in _statusOptions)
|
||||||
ShadOption(value: option, child: Text(option)),
|
ShadOption<String?>(value: option, child: Text(option)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -456,12 +456,12 @@ class _RentalPageState extends State<RentalPage> {
|
|||||||
Text(value ?? RentalTableSpec.allRentalTypeLabel),
|
Text(value ?? RentalTableSpec.allRentalTypeLabel),
|
||||||
onChanged: (value) => setState(() => _pendingRentalType = value),
|
onChanged: (value) => setState(() => _pendingRentalType = value),
|
||||||
options: [
|
options: [
|
||||||
const ShadOption(
|
ShadOption<String?>(
|
||||||
value: null,
|
value: null,
|
||||||
child: Text(RentalTableSpec.allRentalTypeLabel),
|
child: Text(RentalTableSpec.allRentalTypeLabel),
|
||||||
),
|
),
|
||||||
for (final option in RentalTableSpec.rentalTypes)
|
for (final option in RentalTableSpec.rentalTypes)
|
||||||
ShadOption(value: option, child: Text(option)),
|
ShadOption<String?>(value: option, child: Text(option)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -230,6 +230,19 @@ class _InventoryWarehouseSelectFieldState
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final keyword = _controller.text.trim();
|
final keyword = _controller.text.trim();
|
||||||
|
final selectedLabel = _selectedLabel;
|
||||||
|
final matchesSelectedLabel =
|
||||||
|
selectedLabel != null && keyword == selectedLabel;
|
||||||
|
if (matchesSelectedLabel) {
|
||||||
|
_debounce?.cancel();
|
||||||
|
setState(() {
|
||||||
|
_isSearching = false;
|
||||||
|
_suggestions
|
||||||
|
..clear()
|
||||||
|
..addAll(_availableOptions(_initialOptions));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (keyword.isEmpty) {
|
if (keyword.isEmpty) {
|
||||||
_debounce?.cancel();
|
_debounce?.cancel();
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -328,6 +341,14 @@ class _InventoryWarehouseSelectFieldState
|
|||||||
return '${option.name} (${option.code})';
|
return '${option.name} (${option.code})';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? get _selectedLabel {
|
||||||
|
final selected = _selected;
|
||||||
|
if (selected == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return selected.id == -1 ? widget.allLabel : _displayLabel(selected);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildLoadingInput() {
|
Widget _buildLoadingInput() {
|
||||||
return Stack(
|
return Stack(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
@@ -363,13 +384,15 @@ class _InventoryWarehouseSelectFieldState
|
|||||||
textEditingController: _controller,
|
textEditingController: _controller,
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
optionsBuilder: (textEditingValue) {
|
optionsBuilder: (textEditingValue) {
|
||||||
if (textEditingValue.text.trim().isEmpty) {
|
final keyword = textEditingValue.text.trim();
|
||||||
if (_suggestions.isEmpty) {
|
final selectedLabel = _selectedLabel;
|
||||||
|
final shouldShowAll =
|
||||||
|
keyword.isEmpty ||
|
||||||
|
(selectedLabel != null && keyword == selectedLabel);
|
||||||
|
if (shouldShowAll) {
|
||||||
return _availableOptions(_initialOptions);
|
return _availableOptions(_initialOptions);
|
||||||
}
|
}
|
||||||
return _suggestions;
|
return _suggestions;
|
||||||
}
|
|
||||||
return _suggestions;
|
|
||||||
},
|
},
|
||||||
displayStringForOption: (option) =>
|
displayStringForOption: (option) =>
|
||||||
option.id == -1 ? widget.allLabel : _displayLabel(option),
|
option.id == -1 ? widget.allLabel : _displayLabel(option),
|
||||||
|
|||||||
@@ -111,16 +111,15 @@ class FilterBar extends StatelessWidget {
|
|||||||
FilterBarActionConfig config,
|
FilterBarActionConfig config,
|
||||||
) {
|
) {
|
||||||
final theme = ShadTheme.of(context);
|
final theme = ShadTheme.of(context);
|
||||||
if (config.hasPendingChanges) {
|
|
||||||
return ShadBadge(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
child: Text('미적용 변경', style: theme.textTheme.small),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (config.hasActiveFilters) {
|
if (config.hasActiveFilters) {
|
||||||
|
final badgeColor = theme.colorScheme.primary;
|
||||||
return ShadBadge.outline(
|
return ShadBadge.outline(
|
||||||
|
foregroundColor: badgeColor,
|
||||||
|
backgroundColor: badgeColor.withValues(alpha: 0.08),
|
||||||
|
hoverBackgroundColor: badgeColor.withValues(alpha: 0.12),
|
||||||
|
shape: StadiumBorder(
|
||||||
|
side: BorderSide(color: badgeColor.withValues(alpha: 0.4)),
|
||||||
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
child: Text('필터 적용됨', style: theme.textTheme.small),
|
child: Text('필터 적용됨', style: theme.textTheme.small),
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ class SuperportDatePickerButton extends StatelessWidget {
|
|||||||
final format = dateFormat ?? intl.DateFormat('yyyy-MM-dd');
|
final format = dateFormat ?? intl.DateFormat('yyyy-MM-dd');
|
||||||
final displayText = value != null ? format.format(value!) : placeholder;
|
final displayText = value != null ? format.format(value!) : placeholder;
|
||||||
return ShadButton.outline(
|
return ShadButton.outline(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
gap: 8,
|
||||||
onPressed: !enabled
|
onPressed: !enabled
|
||||||
? null
|
? null
|
||||||
: () async {
|
: () async {
|
||||||
@@ -49,14 +51,13 @@ class SuperportDatePickerButton extends StatelessWidget {
|
|||||||
onChanged(picked);
|
onChanged(picked);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Row(
|
trailing: const Icon(lucide.LucideIcons.calendar, size: 16),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
child: Text(
|
||||||
mainAxisSize: MainAxisSize.min,
|
displayText,
|
||||||
children: [
|
maxLines: 1,
|
||||||
Text(displayText),
|
overflow: TextOverflow.ellipsis,
|
||||||
const SizedBox(width: 8),
|
softWrap: false,
|
||||||
const Icon(lucide.LucideIcons.calendar, size: 16),
|
textAlign: TextAlign.left,
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -92,6 +93,8 @@ class SuperportDateRangePickerButton extends StatelessWidget {
|
|||||||
? placeholder
|
? placeholder
|
||||||
: '${format.format(value!.start)} ~ ${format.format(value!.end)}';
|
: '${format.format(value!.start)} ~ ${format.format(value!.end)}';
|
||||||
return ShadButton.outline(
|
return ShadButton.outline(
|
||||||
|
gap: 8,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
onPressed: !enabled
|
onPressed: !enabled
|
||||||
? null
|
? null
|
||||||
: () async {
|
: () async {
|
||||||
@@ -111,14 +114,13 @@ class SuperportDateRangePickerButton extends StatelessWidget {
|
|||||||
onChanged(picked);
|
onChanged(picked);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Row(
|
leading: const Icon(lucide.LucideIcons.calendar, size: 16),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
child: Text(
|
||||||
mainAxisSize: MainAxisSize.min,
|
label,
|
||||||
children: [
|
maxLines: 1,
|
||||||
const Icon(lucide.LucideIcons.calendar, size: 16),
|
overflow: TextOverflow.ellipsis,
|
||||||
const SizedBox(width: 8),
|
softWrap: false,
|
||||||
Flexible(child: Text(label, overflow: TextOverflow.ellipsis)),
|
textAlign: TextAlign.left,
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Reference in New Issue
Block a user