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:
JiWoong Sul
2025-11-10 01:11:04 +09:00
parent 81f419a8a6
commit 04c6bc9a2e
10 changed files with 63 additions and 37 deletions

View File

@@ -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)),
], ],
), ),
), ),

View File

@@ -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;
} }

View File

@@ -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)),
], ],
), ),
), ),

View File

@@ -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)),
], ],
), ),
), ),

View File

@@ -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,11 +384,13 @@ 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;
return _availableOptions(_initialOptions); final shouldShowAll =
} keyword.isEmpty ||
return _suggestions; (selectedLabel != null && keyword == selectedLabel);
if (shouldShowAll) {
return _availableOptions(_initialOptions);
} }
return _suggestions; return _suggestions;
}, },

View File

@@ -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),

View File

@@ -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