자동완성 포커스 유지 및 디버그 로그 추가
This commit is contained in:
@@ -155,16 +155,60 @@ class _ApprovalApproverAutocompleteFieldState
|
|||||||
_applyManualEntry(textController.text);
|
_applyManualEntry(textController.text);
|
||||||
onFieldSubmitted();
|
onFieldSubmitted();
|
||||||
},
|
},
|
||||||
|
onPressedOutside: (event) {
|
||||||
|
// 드롭다운에서 항목을 고르기 전에 포커스를 잃지 않도록 한다.
|
||||||
|
focusNode.requestFocus();
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
optionsViewBuilder: (context, onSelected, options) {
|
optionsViewBuilder: (context, onSelected, options) {
|
||||||
if (options.isEmpty) {
|
if (options.isEmpty) {
|
||||||
return Align(
|
return Listener(
|
||||||
|
onPointerDown: (_) {
|
||||||
|
if (!_focusNode.hasPrimaryFocus) {
|
||||||
|
_focusNode.requestFocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Align(
|
||||||
|
alignment: AlignmentDirectional.topStart,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: constraints.maxWidth,
|
||||||
|
maxHeight: 220,
|
||||||
|
),
|
||||||
|
child: Material(
|
||||||
|
elevation: 6,
|
||||||
|
color: theme.colorScheme.background,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
side: BorderSide(color: theme.colorScheme.border),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||||
|
child: Text(
|
||||||
|
'일치하는 승인자를 찾지 못했습니다.',
|
||||||
|
style: theme.textTheme.muted,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Listener(
|
||||||
|
onPointerDown: (_) {
|
||||||
|
if (!_focusNode.hasPrimaryFocus) {
|
||||||
|
_focusNode.requestFocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Align(
|
||||||
alignment: AlignmentDirectional.topStart,
|
alignment: AlignmentDirectional.topStart,
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: constraints.maxWidth,
|
maxWidth: constraints.maxWidth,
|
||||||
maxHeight: 220,
|
maxHeight: 260,
|
||||||
),
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation: 6,
|
elevation: 6,
|
||||||
@@ -173,64 +217,38 @@ class _ApprovalApproverAutocompleteFieldState
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
side: BorderSide(color: theme.colorScheme.border),
|
side: BorderSide(color: theme.colorScheme.border),
|
||||||
),
|
),
|
||||||
child: Center(
|
child: ListView.builder(
|
||||||
child: Padding(
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
itemCount: options.length,
|
||||||
child: Text(
|
itemBuilder: (context, index) {
|
||||||
'일치하는 승인자를 찾지 못했습니다.',
|
final option = options.elementAt(index);
|
||||||
style: theme.textTheme.muted,
|
return InkWell(
|
||||||
),
|
onTap: () => onSelected(option),
|
||||||
),
|
child: Padding(
|
||||||
),
|
padding: const EdgeInsets.symmetric(
|
||||||
),
|
horizontal: 12,
|
||||||
),
|
vertical: 10,
|
||||||
);
|
),
|
||||||
}
|
child: Column(
|
||||||
return Align(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
alignment: AlignmentDirectional.topStart,
|
children: [
|
||||||
child: ConstrainedBox(
|
Text(
|
||||||
constraints: BoxConstraints(
|
'${option.name} · ${option.team}',
|
||||||
maxWidth: constraints.maxWidth,
|
style: theme.textTheme.p,
|
||||||
maxHeight: 260,
|
|
||||||
),
|
|
||||||
child: Material(
|
|
||||||
elevation: 6,
|
|
||||||
color: theme.colorScheme.background,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
side: BorderSide(color: theme.colorScheme.border),
|
|
||||||
),
|
|
||||||
child: ListView.builder(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
|
||||||
itemCount: options.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final option = options.elementAt(index);
|
|
||||||
return InkWell(
|
|
||||||
onTap: () => onSelected(option),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 12,
|
|
||||||
vertical: 10,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'${option.name} · ${option.team}',
|
|
||||||
style: theme.textTheme.p,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'ID ${option.id} · ${option.employeeNo}',
|
|
||||||
style: theme.textTheme.muted.copyWith(
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 4),
|
||||||
],
|
Text(
|
||||||
|
'ID ${option.id} · ${option.employeeNo}',
|
||||||
|
style: theme.textTheme.muted.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -198,6 +198,10 @@ class _InventoryEmployeeAutocompleteFieldState
|
|||||||
placeholder: Text(widget.placeholder),
|
placeholder: Text(widget.placeholder),
|
||||||
onChanged: (_) => widget.onChanged?.call(),
|
onChanged: (_) => widget.onChanged?.call(),
|
||||||
onSubmitted: (_) => onFieldSubmitted(),
|
onSubmitted: (_) => onFieldSubmitted(),
|
||||||
|
onPressedOutside: (event) {
|
||||||
|
// 드롭다운 선택 중 포커스를 유지해 리스트가 즉시 닫히는 문제를 방지한다.
|
||||||
|
focusNode.requestFocus();
|
||||||
|
},
|
||||||
);
|
);
|
||||||
if (!_isSearching) {
|
if (!_isSearching) {
|
||||||
return input;
|
return input;
|
||||||
@@ -219,61 +223,78 @@ class _InventoryEmployeeAutocompleteFieldState
|
|||||||
},
|
},
|
||||||
optionsViewBuilder: (context, onSelected, options) {
|
optionsViewBuilder: (context, onSelected, options) {
|
||||||
if (options.isEmpty) {
|
if (options.isEmpty) {
|
||||||
return Align(
|
return Listener(
|
||||||
alignment: AlignmentDirectional.topStart,
|
onPointerDown: (_) {
|
||||||
child: Material(
|
// 포커스가 사라지면 항목 선택 전에 닫히므로 즉시 복구한다.
|
||||||
elevation: 6,
|
if (!_focusNode.hasPrimaryFocus) {
|
||||||
color: theme.colorScheme.background,
|
_focusNode.requestFocus();
|
||||||
shape: RoundedRectangleBorder(
|
}
|
||||||
borderRadius: BorderRadius.circular(12),
|
},
|
||||||
side: BorderSide(color: theme.colorScheme.border),
|
child: Align(
|
||||||
),
|
alignment: AlignmentDirectional.topStart,
|
||||||
child: Padding(
|
child: Material(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
elevation: 6,
|
||||||
child: Center(
|
color: theme.colorScheme.background,
|
||||||
child: Text('일치하는 직원이 없습니다.', style: theme.textTheme.muted),
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
side: BorderSide(color: theme.colorScheme.border),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: Center(
|
||||||
|
child: Text('일치하는 직원이 없습니다.', style: theme.textTheme.muted),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Align(
|
return Listener(
|
||||||
alignment: AlignmentDirectional.topStart,
|
onPointerDown: (_) {
|
||||||
child: ConstrainedBox(
|
if (!_focusNode.hasPrimaryFocus) {
|
||||||
constraints: const BoxConstraints(maxHeight: 240, maxWidth: 360),
|
_focusNode.requestFocus();
|
||||||
child: Material(
|
}
|
||||||
elevation: 6,
|
},
|
||||||
color: theme.colorScheme.background,
|
child: Align(
|
||||||
shape: RoundedRectangleBorder(
|
alignment: AlignmentDirectional.topStart,
|
||||||
borderRadius: BorderRadius.circular(12),
|
child: ConstrainedBox(
|
||||||
side: BorderSide(color: theme.colorScheme.border),
|
constraints: const BoxConstraints(maxHeight: 240, maxWidth: 360),
|
||||||
),
|
child: Material(
|
||||||
child: ListView.builder(
|
elevation: 6,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
color: theme.colorScheme.background,
|
||||||
itemCount: options.length,
|
shape: RoundedRectangleBorder(
|
||||||
itemBuilder: (context, index) {
|
borderRadius: BorderRadius.circular(12),
|
||||||
final suggestion = options.elementAt(index);
|
side: BorderSide(color: theme.colorScheme.border),
|
||||||
return InkWell(
|
),
|
||||||
onTap: () => onSelected(suggestion),
|
child: ListView.builder(
|
||||||
child: Padding(
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||||
padding: const EdgeInsets.symmetric(
|
itemCount: options.length,
|
||||||
horizontal: 12,
|
itemBuilder: (context, index) {
|
||||||
vertical: 10,
|
final suggestion = options.elementAt(index);
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => onSelected(suggestion),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(suggestion.name, style: theme.textTheme.p),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'ID ${suggestion.id} · ${suggestion.employeeNo}',
|
||||||
|
style: theme.textTheme.muted.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
);
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
},
|
||||||
children: [
|
),
|
||||||
Text(suggestion.name, style: theme.textTheme.p),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'ID ${suggestion.id} · ${suggestion.employeeNo}',
|
|
||||||
style: theme.textTheme.muted.copyWith(fontSize: 12),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -239,6 +239,10 @@ class _InventoryProductAutocompleteFieldState
|
|||||||
placeholder: const Text('제품명 검색'),
|
placeholder: const Text('제품명 검색'),
|
||||||
onChanged: (_) => widget.onChanged?.call(),
|
onChanged: (_) => widget.onChanged?.call(),
|
||||||
onSubmitted: (_) => onFieldSubmitted(),
|
onSubmitted: (_) => onFieldSubmitted(),
|
||||||
|
onPressedOutside: (event) {
|
||||||
|
// 포커스를 유지해 항목 선택 전 오버레이가 닫히지 않도록 한다.
|
||||||
|
focusNode.requestFocus();
|
||||||
|
},
|
||||||
);
|
);
|
||||||
if (!_isSearching) {
|
if (!_isSearching) {
|
||||||
return input;
|
return input;
|
||||||
@@ -260,12 +264,52 @@ class _InventoryProductAutocompleteFieldState
|
|||||||
},
|
},
|
||||||
optionsViewBuilder: (context, onSelected, options) {
|
optionsViewBuilder: (context, onSelected, options) {
|
||||||
if (options.isEmpty) {
|
if (options.isEmpty) {
|
||||||
return Align(
|
return Listener(
|
||||||
|
onPointerDown: (_) {
|
||||||
|
if (!widget.productFocusNode.hasPrimaryFocus) {
|
||||||
|
widget.productFocusNode.requestFocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Align(
|
||||||
|
alignment: AlignmentDirectional.topStart,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: constraints.maxWidth,
|
||||||
|
maxHeight: 220,
|
||||||
|
),
|
||||||
|
child: Material(
|
||||||
|
elevation: 6,
|
||||||
|
color: theme.colorScheme.background,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
side: BorderSide(color: theme.colorScheme.border),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||||
|
child: Text(
|
||||||
|
'검색 결과가 없습니다.',
|
||||||
|
style: theme.textTheme.muted,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Listener(
|
||||||
|
onPointerDown: (_) {
|
||||||
|
if (!widget.productFocusNode.hasPrimaryFocus) {
|
||||||
|
widget.productFocusNode.requestFocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Align(
|
||||||
alignment: AlignmentDirectional.topStart,
|
alignment: AlignmentDirectional.topStart,
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: constraints.maxWidth,
|
maxWidth: constraints.maxWidth,
|
||||||
maxHeight: 220,
|
maxHeight: 260,
|
||||||
),
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation: 6,
|
elevation: 6,
|
||||||
@@ -274,61 +318,35 @@ class _InventoryProductAutocompleteFieldState
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
side: BorderSide(color: theme.colorScheme.border),
|
side: BorderSide(color: theme.colorScheme.border),
|
||||||
),
|
),
|
||||||
child: Center(
|
child: ListView.builder(
|
||||||
child: Padding(
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
itemCount: options.length,
|
||||||
child: Text(
|
itemBuilder: (context, index) {
|
||||||
'검색 결과가 없습니다.',
|
final option = options.elementAt(index);
|
||||||
style: theme.textTheme.muted,
|
return InkWell(
|
||||||
),
|
onTap: () => onSelected(option),
|
||||||
),
|
child: Padding(
|
||||||
),
|
padding: const EdgeInsets.symmetric(
|
||||||
),
|
horizontal: 12,
|
||||||
),
|
vertical: 10,
|
||||||
);
|
),
|
||||||
}
|
child: Column(
|
||||||
return Align(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
alignment: AlignmentDirectional.topStart,
|
children: [
|
||||||
child: ConstrainedBox(
|
Text(option.name, style: theme.textTheme.p),
|
||||||
constraints: BoxConstraints(
|
const SizedBox(height: 4),
|
||||||
maxWidth: constraints.maxWidth,
|
Text(
|
||||||
maxHeight: 260,
|
'${option.code} · ${option.vendorName} · ${option.unitName}',
|
||||||
),
|
style: theme.textTheme.muted.copyWith(
|
||||||
child: Material(
|
fontSize: 12,
|
||||||
elevation: 6,
|
),
|
||||||
color: theme.colorScheme.background,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
side: BorderSide(color: theme.colorScheme.border),
|
|
||||||
),
|
|
||||||
child: ListView.builder(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
|
||||||
itemCount: options.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final option = options.elementAt(index);
|
|
||||||
return InkWell(
|
|
||||||
onTap: () => onSelected(option),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 12,
|
|
||||||
vertical: 10,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(option.name, style: theme.textTheme.p),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'${option.code} · ${option.vendorName} · ${option.unitName}',
|
|
||||||
style: theme.textTheme.muted.copyWith(
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -395,6 +395,10 @@ class _InventoryWarehouseSelectFieldState
|
|||||||
enabled: widget.enabled,
|
enabled: widget.enabled,
|
||||||
readOnly: !widget.enabled,
|
readOnly: !widget.enabled,
|
||||||
placeholder: placeholder,
|
placeholder: placeholder,
|
||||||
|
onPressedOutside: (event) {
|
||||||
|
// 포커스를 유지하지 않으면 드롭다운이 즉시 닫히므로 재요청한다.
|
||||||
|
focusNode.requestFocus();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (_isSearching)
|
if (_isSearching)
|
||||||
const Padding(
|
const Padding(
|
||||||
@@ -410,41 +414,60 @@ class _InventoryWarehouseSelectFieldState
|
|||||||
},
|
},
|
||||||
optionsViewBuilder: (context, onSelected, options) {
|
optionsViewBuilder: (context, onSelected, options) {
|
||||||
if (options.isEmpty) {
|
if (options.isEmpty) {
|
||||||
return Align(
|
return Listener(
|
||||||
alignment: Alignment.topLeft,
|
onPointerDown: (_) {
|
||||||
child: ConstrainedBox(
|
if (!_focusNode.hasPrimaryFocus) {
|
||||||
constraints: const BoxConstraints(maxHeight: 200, minWidth: 260),
|
_focusNode.requestFocus();
|
||||||
child: ShadCard(
|
}
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
},
|
||||||
child: const Center(child: Text('검색 결과가 없습니다.')),
|
child: Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxHeight: 200,
|
||||||
|
minWidth: 260,
|
||||||
|
),
|
||||||
|
child: ShadCard(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
child: const Center(child: Text('검색 결과가 없습니다.')),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Align(
|
return Listener(
|
||||||
alignment: Alignment.topLeft,
|
onPointerDown: (_) {
|
||||||
child: ConstrainedBox(
|
if (!_focusNode.hasPrimaryFocus) {
|
||||||
constraints: const BoxConstraints(maxHeight: 240, minWidth: 260),
|
_focusNode.requestFocus();
|
||||||
child: ShadCard(
|
}
|
||||||
padding: EdgeInsets.zero,
|
},
|
||||||
child: ListView.builder(
|
child: Align(
|
||||||
shrinkWrap: true,
|
alignment: Alignment.topLeft,
|
||||||
itemCount: options.length,
|
child: ConstrainedBox(
|
||||||
itemBuilder: (context, index) {
|
constraints: const BoxConstraints(maxHeight: 240, minWidth: 260),
|
||||||
final option = options.elementAt(index);
|
child: ShadCard(
|
||||||
final isAll = option.id == -1;
|
padding: EdgeInsets.zero,
|
||||||
final label = isAll ? widget.allLabel : _displayLabel(option);
|
child: ListView.builder(
|
||||||
return InkWell(
|
shrinkWrap: true,
|
||||||
onTap: () => onSelected(option),
|
itemCount: options.length,
|
||||||
child: Padding(
|
itemBuilder: (context, index) {
|
||||||
padding: const EdgeInsets.symmetric(
|
final option = options.elementAt(index);
|
||||||
horizontal: 12,
|
final isAll = option.id == -1;
|
||||||
vertical: 10,
|
final label = isAll
|
||||||
|
? widget.allLabel
|
||||||
|
: _displayLabel(option);
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => onSelected(option),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
child: Text(label),
|
||||||
),
|
),
|
||||||
child: Text(label),
|
);
|
||||||
),
|
},
|
||||||
);
|
),
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ class ProductController extends ChangeNotifier {
|
|||||||
_isLoadingLookups = true;
|
_isLoadingLookups = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
try {
|
try {
|
||||||
|
debugPrint('[ProductController] 드롭다운 데이터 조회 시작');
|
||||||
final vendors = await fetchAllPaginatedItems<Vendor>(
|
final vendors = await fetchAllPaginatedItems<Vendor>(
|
||||||
request: (page, pageSize) =>
|
request: (page, pageSize) =>
|
||||||
_vendorRepository.list(page: page, pageSize: pageSize),
|
_vendorRepository.list(page: page, pageSize: pageSize),
|
||||||
@@ -114,9 +115,16 @@ class ProductController extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
_vendorOptions = vendors;
|
_vendorOptions = vendors;
|
||||||
_uomOptions = uoms;
|
_uomOptions = uoms;
|
||||||
|
debugPrint(
|
||||||
|
'[ProductController] 드롭다운 데이터 조회 완료 '
|
||||||
|
'(vendors=${vendors.length}, uoms=${uoms.length})',
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
final failure = Failure.from(error);
|
final failure = Failure.from(error);
|
||||||
_errorMessage = failure.describe();
|
_errorMessage = failure.describe();
|
||||||
|
debugPrint(
|
||||||
|
'[ProductController] 드롭다운 데이터 조회 실패: ${_errorMessage ?? '알 수 없는 오류'}',
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
_isLoadingLookups = false;
|
_isLoadingLookups = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|||||||
@@ -345,8 +345,8 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
|
|||||||
size: ShadButtonSize.sm,
|
size: ShadButtonSize.sm,
|
||||||
onPressed:
|
onPressed:
|
||||||
_controller.isLoading || currentPage >= totalPages
|
_controller.isLoading || currentPage >= totalPages
|
||||||
? null
|
? null
|
||||||
: () => _goToPage(totalPages),
|
: () => _goToPage(totalPages),
|
||||||
child: const Text('마지막'),
|
child: const Text('마지막'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -633,8 +633,7 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
|
|||||||
return ShadButton.ghost(
|
return ShadButton.ghost(
|
||||||
onPressed: isSaving
|
onPressed: isSaving
|
||||||
? null
|
? null
|
||||||
: () =>
|
: () => Navigator.of(context, rootNavigator: true).pop(false),
|
||||||
Navigator.of(context, rootNavigator: true).pop(false),
|
|
||||||
child: const Text('취소'),
|
child: const Text('취소'),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -739,6 +738,9 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
|
|||||||
enabled: !isSaving,
|
enabled: !isSaving,
|
||||||
placeholder: const Text('제조사를 선택하세요'),
|
placeholder: const Text('제조사를 선택하세요'),
|
||||||
onSelected: (id) {
|
onSelected: (id) {
|
||||||
|
debugPrint(
|
||||||
|
'[ProductForm] 제조사 선택 -> id=$id',
|
||||||
|
);
|
||||||
vendorNotifier.value = id;
|
vendorNotifier.value = id;
|
||||||
vendorError.value = null;
|
vendorError.value = null;
|
||||||
},
|
},
|
||||||
@@ -780,6 +782,7 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
|
|||||||
enabled: !isSaving,
|
enabled: !isSaving,
|
||||||
placeholder: const Text('단위를 선택하세요'),
|
placeholder: const Text('단위를 선택하세요'),
|
||||||
onSelected: (id) {
|
onSelected: (id) {
|
||||||
|
debugPrint('[ProductForm] 단위 선택 -> id=$id');
|
||||||
uomNotifier.value = id;
|
uomNotifier.value = id;
|
||||||
uomError.value = null;
|
uomError.value = null;
|
||||||
},
|
},
|
||||||
@@ -867,8 +870,7 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
|
|||||||
title: '제품 삭제',
|
title: '제품 삭제',
|
||||||
description: '"${product.productName}" 제품을 삭제하시겠습니까?',
|
description: '"${product.productName}" 제품을 삭제하시겠습니까?',
|
||||||
primaryAction: ShadButton.destructive(
|
primaryAction: ShadButton.destructive(
|
||||||
onPressed: () =>
|
onPressed: () => Navigator.of(context, rootNavigator: true).pop(true),
|
||||||
Navigator.of(context, rootNavigator: true).pop(true),
|
|
||||||
child: const Text('삭제'),
|
child: const Text('삭제'),
|
||||||
),
|
),
|
||||||
secondaryAction: ShadButton.ghost(
|
secondaryAction: ShadButton.ghost(
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_controller.addListener(_handleTextChanged);
|
_controller.addListener(_handleTextChanged);
|
||||||
|
_focusNode.addListener(_handleFocusChange);
|
||||||
_initializeOptions();
|
_initializeOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +76,9 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_controller.removeListener(_handleTextChanged);
|
_controller.removeListener(_handleTextChanged);
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
_focusNode.dispose();
|
_focusNode
|
||||||
|
..removeListener(_handleFocusChange)
|
||||||
|
..dispose();
|
||||||
_debounce?.cancel();
|
_debounce?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -96,6 +99,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
|||||||
);
|
);
|
||||||
_isApplyingText = false;
|
_isApplyingText = false;
|
||||||
}
|
}
|
||||||
|
_log('초기 옵션 준비 완료 (base=${_baseOptions.length})');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _resetSuggestions() {
|
void _resetSuggestions() {
|
||||||
@@ -104,6 +108,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
|||||||
..clear()
|
..clear()
|
||||||
..addAll(_baseOptions);
|
..addAll(_baseOptions);
|
||||||
});
|
});
|
||||||
|
_log('입력 초기화 -> 제안 목록 리셋 (count=${_suggestions.length})');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleTextChanged() {
|
void _handleTextChanged() {
|
||||||
@@ -127,6 +132,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
|||||||
void _scheduleSearch(String keyword) {
|
void _scheduleSearch(String keyword) {
|
||||||
_debounce?.cancel();
|
_debounce?.cancel();
|
||||||
_debounce = Timer(_debounceDuration, () => _search(keyword));
|
_debounce = Timer(_debounceDuration, () => _search(keyword));
|
||||||
|
_log('검색 예약: "$keyword"');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _search(String keyword) async {
|
Future<void> _search(String keyword) async {
|
||||||
@@ -143,6 +149,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
|||||||
_error = null;
|
_error = null;
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
|
_log('단위 검색 시작: "$keyword"');
|
||||||
final result = await repository.list(
|
final result = await repository.list(
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
@@ -159,6 +166,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
|||||||
..addAll(items);
|
..addAll(items);
|
||||||
_isSearching = false;
|
_isSearching = false;
|
||||||
});
|
});
|
||||||
|
_log('단위 검색 완료: ${items.length}건');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!mounted || request != _requestId) {
|
if (!mounted || request != _requestId) {
|
||||||
return;
|
return;
|
||||||
@@ -169,6 +177,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
|||||||
_suggestions.clear();
|
_suggestions.clear();
|
||||||
_isSearching = false;
|
_isSearching = false;
|
||||||
});
|
});
|
||||||
|
_log('단위 검색 실패: ${_error ?? '알 수 없는 오류'}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,6 +217,11 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
|||||||
}
|
}
|
||||||
_applyControllerText(uom?.uomName ?? '');
|
_applyControllerText(uom?.uomName ?? '');
|
||||||
});
|
});
|
||||||
|
if (uom == null) {
|
||||||
|
_log('선택 해제됨');
|
||||||
|
} else {
|
||||||
|
_log('항목 선택: id=${uom.id}, label=${uom.uomName}');
|
||||||
|
}
|
||||||
if (notify) {
|
if (notify) {
|
||||||
widget.onSelected(uom?.id);
|
widget.onSelected(uom?.id);
|
||||||
}
|
}
|
||||||
@@ -249,6 +263,13 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
|||||||
enabled: widget.enabled,
|
enabled: widget.enabled,
|
||||||
readOnly: !widget.enabled,
|
readOnly: !widget.enabled,
|
||||||
placeholder: placeholder,
|
placeholder: placeholder,
|
||||||
|
onPressedOutside: (event) {
|
||||||
|
_log(
|
||||||
|
'입력 외부 포인터 감지 -> 포커스 유지 시도 '
|
||||||
|
'pos=${event.localPosition} kind=${event.kind}',
|
||||||
|
);
|
||||||
|
focusNode.requestFocus();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (_isSearching)
|
if (_isSearching)
|
||||||
const Padding(
|
const Padding(
|
||||||
@@ -264,39 +285,75 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
|||||||
},
|
},
|
||||||
optionsViewBuilder: (context, onSelected, options) {
|
optionsViewBuilder: (context, onSelected, options) {
|
||||||
if (options.isEmpty) {
|
if (options.isEmpty) {
|
||||||
return Align(
|
return Listener(
|
||||||
alignment: Alignment.topLeft,
|
onPointerDown: (event) {
|
||||||
child: ConstrainedBox(
|
_log(
|
||||||
constraints: const BoxConstraints(maxHeight: 200, minWidth: 220),
|
'오버레이 포인터 다운 (빈 목록) '
|
||||||
child: ShadCard(
|
'pos=${event.localPosition} kind=${event.kind}',
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
);
|
||||||
child: const Center(child: Text('검색 결과가 없습니다.')),
|
if (!_focusNode.hasPrimaryFocus) {
|
||||||
|
_log('포커스 복구 시도 (빈 목록)');
|
||||||
|
_focusNode.requestFocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxHeight: 200,
|
||||||
|
minWidth: 220,
|
||||||
|
),
|
||||||
|
child: ShadCard(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
child: const Center(child: Text('검색 결과가 없습니다.')),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Align(
|
_log('드롭다운 리스트 표시: ${options.length}건');
|
||||||
alignment: Alignment.topLeft,
|
return Listener(
|
||||||
child: ConstrainedBox(
|
onPointerDown: (event) {
|
||||||
constraints: const BoxConstraints(maxHeight: 240, minWidth: 220),
|
_log(
|
||||||
child: ShadCard(
|
'오버레이 포인터 다운 pos=${event.localPosition} '
|
||||||
padding: EdgeInsets.zero,
|
'kind=${event.kind}',
|
||||||
child: ListView.builder(
|
);
|
||||||
shrinkWrap: true,
|
if (!_focusNode.hasPrimaryFocus) {
|
||||||
itemCount: options.length,
|
_log('포커스 복구 시도');
|
||||||
itemBuilder: (context, index) {
|
_focusNode.requestFocus();
|
||||||
final uom = options.elementAt(index);
|
}
|
||||||
return InkWell(
|
},
|
||||||
onTap: () => onSelected(uom),
|
child: Align(
|
||||||
child: Padding(
|
alignment: Alignment.topLeft,
|
||||||
padding: const EdgeInsets.symmetric(
|
child: ConstrainedBox(
|
||||||
horizontal: 12,
|
constraints: const BoxConstraints(maxHeight: 240, minWidth: 220),
|
||||||
vertical: 10,
|
child: ShadCard(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: options.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final uom = options.elementAt(index);
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTapDown: (_) =>
|
||||||
|
_log('항목 탭다운: id=${uom.id} index=$index'),
|
||||||
|
onTapCancel: () =>
|
||||||
|
_log('항목 탭 취소: id=${uom.id} index=$index'),
|
||||||
|
onTap: () {
|
||||||
|
_log('항목 탭 -> onSelected 호출: id=${uom.id}');
|
||||||
|
onSelected(uom);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
child: Text(uom.uomName),
|
||||||
),
|
),
|
||||||
child: Text(uom.uomName),
|
);
|
||||||
),
|
},
|
||||||
);
|
),
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -310,5 +367,18 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
|||||||
_controller.text = value;
|
_controller.text = value;
|
||||||
_controller.selection = TextSelection.collapsed(offset: value.length);
|
_controller.selection = TextSelection.collapsed(offset: value.length);
|
||||||
_isApplyingText = false;
|
_isApplyingText = false;
|
||||||
|
_log('입력 필드 텍스트 적용: "$value"');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleFocusChange() {
|
||||||
|
if (_focusNode.hasFocus) {
|
||||||
|
_log('포커스 획득 -> 드롭다운 진입');
|
||||||
|
} else {
|
||||||
|
_log('포커스 해제 -> 드롭다운 종료');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _log(String message) {
|
||||||
|
debugPrint('[UomAutocomplete] $message');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_controller.addListener(_handleTextChanged);
|
_controller.addListener(_handleTextChanged);
|
||||||
|
_focusNode.addListener(_handleFocusChange);
|
||||||
_initializeOptions();
|
_initializeOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +81,9 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_controller.removeListener(_handleTextChanged);
|
_controller.removeListener(_handleTextChanged);
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
_focusNode.dispose();
|
_focusNode
|
||||||
|
..removeListener(_handleFocusChange)
|
||||||
|
..dispose();
|
||||||
_debounce?.cancel();
|
_debounce?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -104,6 +107,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
|||||||
);
|
);
|
||||||
_isApplyingText = false;
|
_isApplyingText = false;
|
||||||
}
|
}
|
||||||
|
_log('초기 옵션 준비 완료 (base=${_baseOptions.length})');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _resetSuggestions() {
|
void _resetSuggestions() {
|
||||||
@@ -112,6 +116,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
|||||||
..clear()
|
..clear()
|
||||||
..addAll(_baseOptions);
|
..addAll(_baseOptions);
|
||||||
});
|
});
|
||||||
|
_log('입력 초기화 -> 제안 목록 리셋 (count=${_suggestions.length})');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleTextChanged() {
|
void _handleTextChanged() {
|
||||||
@@ -135,6 +140,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
|||||||
void _scheduleSearch(String keyword) {
|
void _scheduleSearch(String keyword) {
|
||||||
_debounce?.cancel();
|
_debounce?.cancel();
|
||||||
_debounce = Timer(_debounceDuration, () => _search(keyword));
|
_debounce = Timer(_debounceDuration, () => _search(keyword));
|
||||||
|
_log('검색 예약: "$keyword"');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _search(String keyword) async {
|
Future<void> _search(String keyword) async {
|
||||||
@@ -151,6 +157,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
|||||||
_error = null;
|
_error = null;
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
|
_log('공급업체 검색 시작: "$keyword"');
|
||||||
final result = await repository.list(
|
final result = await repository.list(
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
@@ -167,6 +174,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
|||||||
..addAll(items);
|
..addAll(items);
|
||||||
_isSearching = false;
|
_isSearching = false;
|
||||||
});
|
});
|
||||||
|
_log('공급업체 검색 완료: ${items.length}건');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!mounted || request != _requestId) {
|
if (!mounted || request != _requestId) {
|
||||||
return;
|
return;
|
||||||
@@ -177,6 +185,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
|||||||
_suggestions.clear();
|
_suggestions.clear();
|
||||||
_isSearching = false;
|
_isSearching = false;
|
||||||
});
|
});
|
||||||
|
_log('공급업체 검색 실패: ${_error ?? '알 수 없는 오류'}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,6 +229,11 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
|||||||
_applyControllerText(_displayLabel(vendor));
|
_applyControllerText(_displayLabel(vendor));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (vendor == null) {
|
||||||
|
_log('선택 해제됨');
|
||||||
|
} else {
|
||||||
|
_log('항목 선택: id=${vendor.id}, label=${_displayLabel(vendor)}');
|
||||||
|
}
|
||||||
if (notify) {
|
if (notify) {
|
||||||
widget.onSelected(vendor?.id);
|
widget.onSelected(vendor?.id);
|
||||||
}
|
}
|
||||||
@@ -266,6 +280,13 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
|||||||
enabled: widget.enabled,
|
enabled: widget.enabled,
|
||||||
readOnly: !widget.enabled,
|
readOnly: !widget.enabled,
|
||||||
placeholder: placeholder,
|
placeholder: placeholder,
|
||||||
|
onPressedOutside: (event) {
|
||||||
|
_log(
|
||||||
|
'입력 외부 포인터 감지 -> 포커스 유지 시도 '
|
||||||
|
'pos=${event.localPosition} kind=${event.kind}',
|
||||||
|
);
|
||||||
|
focusNode.requestFocus();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (_isSearching)
|
if (_isSearching)
|
||||||
const Padding(
|
const Padding(
|
||||||
@@ -281,39 +302,75 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
|||||||
},
|
},
|
||||||
optionsViewBuilder: (context, onSelected, options) {
|
optionsViewBuilder: (context, onSelected, options) {
|
||||||
if (options.isEmpty) {
|
if (options.isEmpty) {
|
||||||
return Align(
|
return Listener(
|
||||||
alignment: Alignment.topLeft,
|
onPointerDown: (event) {
|
||||||
child: ConstrainedBox(
|
_log(
|
||||||
constraints: const BoxConstraints(maxHeight: 200, minWidth: 260),
|
'오버레이 포인터 다운 (빈 목록) '
|
||||||
child: ShadCard(
|
'pos=${event.localPosition} kind=${event.kind}',
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
);
|
||||||
child: const Center(child: Text('검색 결과가 없습니다.')),
|
if (!_focusNode.hasPrimaryFocus) {
|
||||||
|
_log('포커스 복구 시도 (빈 목록)');
|
||||||
|
_focusNode.requestFocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxHeight: 200,
|
||||||
|
minWidth: 260,
|
||||||
|
),
|
||||||
|
child: ShadCard(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
child: const Center(child: Text('검색 결과가 없습니다.')),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Align(
|
_log('드롭다운 리스트 표시: ${options.length}건');
|
||||||
alignment: Alignment.topLeft,
|
return Listener(
|
||||||
child: ConstrainedBox(
|
onPointerDown: (event) {
|
||||||
constraints: const BoxConstraints(maxHeight: 240, minWidth: 260),
|
_log(
|
||||||
child: ShadCard(
|
'오버레이 포인터 다운 pos=${event.localPosition} '
|
||||||
padding: EdgeInsets.zero,
|
'kind=${event.kind}',
|
||||||
child: ListView.builder(
|
);
|
||||||
shrinkWrap: true,
|
if (!_focusNode.hasPrimaryFocus) {
|
||||||
itemCount: options.length,
|
_log('포커스 복구 시도');
|
||||||
itemBuilder: (context, index) {
|
_focusNode.requestFocus();
|
||||||
final vendor = options.elementAt(index);
|
}
|
||||||
return InkWell(
|
},
|
||||||
onTap: () => onSelected(vendor),
|
child: Align(
|
||||||
child: Padding(
|
alignment: Alignment.topLeft,
|
||||||
padding: const EdgeInsets.symmetric(
|
child: ConstrainedBox(
|
||||||
horizontal: 12,
|
constraints: const BoxConstraints(maxHeight: 240, minWidth: 260),
|
||||||
vertical: 10,
|
child: ShadCard(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: options.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final vendor = options.elementAt(index);
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTapDown: (_) =>
|
||||||
|
_log('항목 탭다운: id=${vendor.id} index=$index'),
|
||||||
|
onTapCancel: () =>
|
||||||
|
_log('항목 탭 취소: id=${vendor.id} index=$index'),
|
||||||
|
onTap: () {
|
||||||
|
_log('항목 탭 -> onSelected 호출: id=${vendor.id}');
|
||||||
|
onSelected(vendor);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
child: Text(_displayLabel(vendor)),
|
||||||
),
|
),
|
||||||
child: Text(_displayLabel(vendor)),
|
);
|
||||||
),
|
},
|
||||||
);
|
),
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -327,5 +384,18 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
|||||||
_controller.text = value;
|
_controller.text = value;
|
||||||
_controller.selection = TextSelection.collapsed(offset: value.length);
|
_controller.selection = TextSelection.collapsed(offset: value.length);
|
||||||
_isApplyingText = false;
|
_isApplyingText = false;
|
||||||
|
_log('입력 필드 텍스트 적용: "$value"');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleFocusChange() {
|
||||||
|
if (_focusNode.hasFocus) {
|
||||||
|
_log('포커스 획득 -> 드롭다운 진입');
|
||||||
|
} else {
|
||||||
|
_log('포커스 해제 -> 드롭다운 종료');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _log(String message) {
|
||||||
|
debugPrint('[VendorAutocomplete] $message');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user