Compare commits

..

3 Commits

Author SHA1 Message Date
94d41194d1 fix:search api in workflow lookup 2026-08-18 13:34:38 +05:30
caf15840aa feat/search api is added 2026-08-18 13:19:58 +05:30
060ec00d8d fix: bar graph fixed 2026-08-18 12:38:35 +05:30
8 changed files with 72 additions and 106 deletions

View File

@ -271,5 +271,5 @@ export const HIDDEN_FORM_FIELDS = [
'store_code_3' 'store_code_3'
]; ];
export const LOOKUP_RECORD_LIMIT = 500; export const LOOKUP_RECORD_LIMIT = 200;
export const MAX_SKUS_PER_CHUNK = 20; export const MAX_SKUS_PER_CHUNK = 20;

View File

@ -187,8 +187,8 @@ export function StoreDetail({ instanceId, onBack, onEdit, refreshKey }: StoreDet
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200 shadow-sm overflow-hidden flex flex-col md:flex-row"> <div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200 shadow-sm overflow-hidden flex flex-col md:flex-row">
{/* Image Section */} {/* Image Section */}
{imageUrl ? ( {imageUrl ? (
<div className="md:w-1/3 lg:w-1/4 h-48 md:h-auto relative bg-slate-100"> <div className="md:w-1/3 lg:w-1/4 h-48 md:h-auto relative bg-slate-100 shrink-0">
<img src={imageUrl} alt={String(businessName)} className="w-full h-full object-cover" /> <img src={imageUrl} alt={String(businessName)} className="absolute inset-0 w-full h-full object-cover" />
</div> </div>
) : ( ) : (
<div className="md:w-1/3 lg:w-1/4 h-48 md:h-auto flex items-center justify-center bg-slate-100 border-r border-slate-200"> <div className="md:w-1/3 lg:w-1/4 h-48 md:h-auto flex items-center justify-center bg-slate-100 border-r border-slate-200">
@ -321,32 +321,32 @@ export function StoreDetail({ instanceId, onBack, onEdit, refreshKey }: StoreDet
{/* Order Potential Card */} {/* Order Potential Card */}
{Array.isArray(potential) && potential.length > 0 && ( {Array.isArray(potential) && potential.length > 0 && (
<Card pad={false} className="overflow-hidden border-slate-200 shadow-sm"> <Card pad={false} className="overflow-hidden border-emerald-200 shadow-sm">
<div className="flex items-center gap-2 p-4 border-b border-slate-100 bg-[var(--tiles-card-bg)]"> <div className="flex items-center gap-2 p-4 border-b border-emerald-100 bg-emerald-50">
<FileText size={16} className="text-slate-400" /> <FileText size={16} className="text-emerald-600" />
<h3 className="text-xs font-bold text-slate-500 uppercase tracking-wider m-0">Order Potential</h3> <h3 className="text-xs font-bold text-emerald-800 uppercase tracking-wider m-0">Order Potential</h3>
</div> </div>
<div className="bg-[var(--tiles-card-bg)]"> <div className="bg-[var(--tiles-card-bg)]">
<table className="w-full text-sm text-left"> <table className="w-full text-sm text-left">
<thead className="bg-slate-50 border-b border-slate-100"> <thead className="bg-emerald-50 border-b border-emerald-100">
<tr> <tr>
<th className="py-3 px-5 font-bold text-slate-500 text-xs uppercase tracking-wider">Product Category</th> <th className="py-3 px-5 font-bold text-emerald-800 text-xs uppercase tracking-wider">Product Category</th>
<th className="py-3 px-5 font-bold text-slate-500 text-xs uppercase tracking-wider text-right">Quantity</th> <th className="py-3 px-5 font-bold text-emerald-800 text-xs uppercase tracking-wider text-right">Quantity</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{potential.map((p: any, idx: number) => { {potential.map((p: any, idx: number) => {
if (!p.product_category && !p.quantity) return null; if (!p.product_category && !p.quantity) return null;
return ( return (
<tr key={idx} className="border-b border-slate-100 last:border-0 hover:bg-slate-50/50"> <tr key={idx} className="border-b border-emerald-50 last:border-0 hover:bg-emerald-50/50">
<td className="py-3 px-5 font-semibold text-slate-900">{p.product_category ? String(p.product_category) : '-'}</td> <td className="py-3 px-5 font-semibold text-emerald-900">{p.product_category ? String(p.product_category) : '-'}</td>
<td className="py-3 px-5 text-slate-700 text-right">{p.quantity ? String(p.quantity) : '0'}</td> <td className="py-3 px-5 text-emerald-700 text-right">{p.quantity ? String(p.quantity) : '0'}</td>
</tr> </tr>
); );
})} })}
<tr className="bg-slate-50 border-t border-slate-200 font-bold"> <tr className="bg-emerald-50 border-t border-emerald-200 font-bold">
<td className="py-3 px-5 text-slate-900">Total</td> <td className="py-3 px-5 text-emerald-900">Total</td>
<td className="py-3 px-5 text-slate-900 text-right">{totalPotential}</td> <td className="py-3 px-5 text-emerald-900 text-right">{totalPotential}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -174,43 +174,6 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
} }
} }
if (currentActivityId === ORDER_BOOKING.activities.LOG_VISIT.uid) {
const user = client.currentUser();
const userMobile = user?.mobile || localStorage.getItem('krishna_sales_user_mobile') || user?.email;
if (userMobile) {
try {
const headers: Record<string, string> = {
'templateid': '192',
'x-pipeline-version': 'latest',
'orgid': '57',
'groupid': '25'
};
const summaryRes = await client.request<any>(
'POST',
PIPELINE.endpoints.productiveCallSummary,
{ mobile: userMobile },
headers
);
let summaryData = summaryRes.data || summaryRes;
if (Array.isArray(summaryData)) {
summaryData = summaryData[0] || {};
}
if (summaryData.productive_call !== undefined) {
summaryData.total_productive_calls = summaryData.productive_call;
}
if (summaryData.non_productive_call !== undefined) {
summaryData.total_non_productive_calls = summaryData.non_productive_call;
}
mapPrefillData(summaryData);
} catch (e) {
console.warn('Failed to load productive call summary for Log Visit', e);
}
}
}
setValues(defaultValues); setValues(defaultValues);
setLoading(false); setLoading(false);
if (res.activity_name) { if (res.activity_name) {

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect, useCallback, useRef } from 'react'; import React, { useState, useEffect, useCallback, useRef } from 'react';
import type { ZinoClient } from '../../api/client'; import type { ZinoClient } from '../../api/client';
import type { FormScreenResponse } from '../../api/types'; import type { FormScreenResponse } from '../../api/types';
import { ORDER_BOOKING, PIPELINE, LOOKUP_RECORD_LIMIT } from '../../api/config'; import { ORDER_BOOKING, LOOKUP_RECORD_LIMIT } from '../../api/config';
import { Button } from '../buttons/Button'; import { Button } from '../buttons/Button';
import { Select } from '../reusable/Select'; import { Select } from '../reusable/Select';
import { DateField, TimeField, FileInput, TextField } from './fields'; import { DateField, TimeField, FileInput, TextField } from './fields';
@ -61,9 +61,9 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
}, [values]); }, [values]);
const [storeOptions, setStoreOptions] = useState<{ value: string; label: string; _raw?: any }[]>([]); const [storeOptions, setStoreOptions] = useState<{ value: string; label: string; _raw?: any }[]>([]);
const [storeSearchQuery, setStoreSearchQuery] = useState('');
const [fetchingDailyLog, setFetchingDailyLog] = useState(false); const [fetchingDailyLog, setFetchingDailyLog] = useState(false);
const [fetchingStores, setFetchingStores] = useState(false); const [fetchingStores, setFetchingStores] = useState(false);
const fetchingStoresRef = useRef(false);
const [chainedActivity, setChainedActivity] = useState<{ const [chainedActivity, setChainedActivity] = useState<{
activityId: string; activityId: string;
@ -119,40 +119,6 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
Object.assign(initialValues, res.data); Object.assign(initialValues, res.data);
} }
// Execute mobile pipeline prefill
const user = client.currentUser();
const userMobile = user?.mobile || localStorage.getItem('krishna_sales_user_mobile') || user?.email;
if (userMobile) {
try {
const headers: Record<string, string> = {
'templateid': '192',
'x-pipeline-version': 'latest',
'orgid': '57',
'groupid': '25'
};
const summaryRes = await client.request<any>(
'POST',
PIPELINE.endpoints.productiveCallSummary,
{ mobile: userMobile },
headers
);
let summaryData = summaryRes.data || summaryRes;
if (Array.isArray(summaryData)) {
summaryData = summaryData[0] || {};
}
if (summaryData.productive_call !== undefined) {
summaryData.total_productive_calls = summaryData.productive_call;
}
if (summaryData.non_productive_call !== undefined) {
summaryData.total_non_productive_calls = summaryData.non_productive_call;
}
Object.assign(initialValues, summaryData);
} catch (e) {
console.warn('Failed to load productive call summary for Log Visit', e);
}
}
setValues(prev => { setValues(prev => {
const next = { ...initialValues, ...prev }; const next = { ...initialValues, ...prev };
valuesRef.current = next; valuesRef.current = next;
@ -170,9 +136,7 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
}, [client, activityId, todayStr, nowTimeStr]); }, [client, activityId, todayStr, nowTimeStr]);
// 2. Helper to fetch select_store options ONLY using updated prefilled formData // 2. Helper to fetch select_store options ONLY using updated prefilled formData
const fetchStoreOptions = useCallback(async (formDataOverride?: Record<string, any>) => { const fetchStoreOptions = useCallback(async (formDataOverride?: Record<string, any>, search: string = '') => {
if (fetchingStoresRef.current) return;
fetchingStoresRef.current = true;
setFetchingStores(true); setFetchingStores(true);
setStoreError(null); setStoreError(null);
@ -184,6 +148,7 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
activityId, activityId,
fieldId: 'select_store', fieldId: 'select_store',
formData: formDataToSend, formData: formDataToSend,
search,
limit: LOOKUP_RECORD_LIMIT, limit: LOOKUP_RECORD_LIMIT,
}); });
@ -234,10 +199,16 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
} }
} finally { } finally {
setFetchingStores(false); setFetchingStores(false);
fetchingStoresRef.current = false;
} }
}, [client, activityId]); }, [client, activityId]);
useEffect(() => {
const timer = setTimeout(() => {
fetchStoreOptions(undefined, storeSearchQuery);
}, 300);
return () => clearTimeout(timer);
}, [storeSearchQuery, fetchStoreOptions]);
// 3. Fetch Daily Log lookup data initially & prefill form state // 3. Fetch Daily Log lookup data initially & prefill form state
const loadDailyLogData = useCallback(async (dateVal: string, timeVal: string) => { const loadDailyLogData = useCallback(async (dateVal: string, timeVal: string) => {
setFetchingDailyLog(true); setFetchingDailyLog(true);
@ -464,6 +435,8 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
options={[{ value: '', label: `Select ${f.name}...` }, ...storeOptions]} options={[{ value: '', label: `Select ${f.name}...` }, ...storeOptions]}
onDropdownOpen={handleStoreDropdownOpen} onDropdownOpen={handleStoreDropdownOpen}
onChange={e => handleStoreSelect(fieldId, e.target.value)} onChange={e => handleStoreSelect(fieldId, e.target.value)}
onSearchChange={setStoreSearchQuery}
disableLocalSearch={true}
disabled={isDisabled} disabled={isDisabled}
/> />
{fetchingStores && ( {fetchingStores && (

View File

@ -6,12 +6,16 @@ export function SelectField({
value, value,
options, options,
onChange, onChange,
onSearchChange,
disableLocalSearch,
}: { }: {
label: string; label: string;
required?: boolean; required?: boolean;
value: string; value: string;
options: any[]; options: any[];
onChange: (val: string, fullRow?: any) => void; onChange: (val: string, fullRow?: any) => void;
onSearchChange?: (query: string) => void;
disableLocalSearch?: boolean;
}) { }) {
return ( return (
<Select <Select
@ -24,6 +28,8 @@ export function SelectField({
onChange(val, opt?._raw); onChange(val, opt?._raw);
}} }}
options={[{ value: '', label: 'Select...' }, ...options.map((o: any) => ({ value: String(o.value), label: o.label }))]} options={[{ value: '', label: 'Select...' }, ...options.map((o: any) => ({ value: String(o.value), label: o.label }))]}
onSearchChange={onSearchChange}
disableLocalSearch={disableLocalSearch}
/> />
); );
} }

View File

@ -20,6 +20,7 @@ export function WfLookupField({ label, required, value, onChange, client, config
const [options, setOptions] = useState<{ label: string; value: string }[]>([]); const [options, setOptions] = useState<{ label: string; value: string }[]>([]);
const [records, setRecords] = useState<any[]>([]); const [records, setRecords] = useState<any[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
const formDataStr = JSON.stringify(formData); const formDataStr = JSON.stringify(formData);
@ -32,6 +33,7 @@ export function WfLookupField({ label, required, value, onChange, client, config
activityId, activityId,
fieldId, fieldId,
formData: JSON.parse(formDataStr), formData: JSON.parse(formDataStr),
search: searchQuery,
limit: LOOKUP_RECORD_LIMIT limit: LOOKUP_RECORD_LIMIT
}) })
.then(res => { .then(res => {
@ -72,7 +74,7 @@ export function WfLookupField({ label, required, value, onChange, client, config
mounted = false; mounted = false;
clearTimeout(timer); clearTimeout(timer);
}; };
}, [client, config, activityId, fieldId, formDataStr]); }, [client, config, activityId, fieldId, formDataStr, searchQuery]);
return ( return (
<SelectField <SelectField
@ -84,6 +86,8 @@ export function WfLookupField({ label, required, value, onChange, client, config
onChange(val, row); onChange(val, row);
}} }}
options={options} options={options}
onSearchChange={setSearchQuery}
disableLocalSearch={true}
/> />
); );
} }

View File

@ -126,20 +126,21 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
interval={0} interval={0}
dy={10} dy={10}
dx={-5} dx={-5}
height={70} height={90}
/> />
<YAxis <YAxis
axisLine={false} axisLine={false}
tickLine={false} tickLine={false}
tick={{ fontSize: 12, fill: '#64748B' }} tick={{ fontSize: 12, fill: '#64748B' }}
allowDecimals={false} allowDecimals={false}
width={80} width={40}
/> />
<Tooltip <Tooltip
cursor={{ fill: '#F8FAFC', stroke: '#E2E8F0', strokeWidth: 1, strokeDasharray: '3 3' }} cursor={{ fill: '#F8FAFC', stroke: '#E2E8F0', strokeWidth: 1, strokeDasharray: '3 3' }}
contentStyle={{ borderRadius: '8px', border: '1px solid #E2E8F0', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)', fontSize: '14px', fontFamily: 'inherit' }} contentStyle={{ borderRadius: '8px', border: '1px solid #E2E8F0', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)', fontSize: '14px', fontFamily: 'inherit' }}
labelStyle={{ fontWeight: 'bold', color: '#0F172A', marginBottom: '4px' }} labelStyle={{ fontWeight: 'bold', color: '#0F172A', marginBottom: '4px' }}
wrapperStyle={{ zIndex: 1000 }} wrapperStyle={{ zIndex: 1000 }}
allowEscapeViewBox={{ x: true, y: true }}
/> />
{hasSeries && <Legend wrapperStyle={{ paddingTop: '20px' }} />} {hasSeries && <Legend wrapperStyle={{ paddingTop: '20px' }} />}
{seriesKeys.map((key, i) => { {seriesKeys.map((key, i) => {
@ -189,7 +190,7 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
}; };
return ( return (
<Card key={chart.chart_uid || idx} title={title} className={`shadow-sm border border-gray-100 !bg-[var(--tiles-card-bg)] ${isFullWidth ? fullSpanClass : ''}`}> <Card key={chart.chart_uid || idx} title={title} className={`shadow-sm border border-gray-100 !bg-[var(--tiles-card-bg)] ${isFullWidth ? fullSpanClass : ''} !overflow-visible`}>
<div className={`h-[320px] w-full mt-4 ${minChartWidth > 100 ? 'overflow-x-auto scrollbar-slim' : ''}`}> <div className={`h-[320px] w-full mt-4 ${minChartWidth > 100 ? 'overflow-x-auto scrollbar-slim' : ''}`}>
{chartType === 5 ? ( {chartType === 5 ? (
<div className="w-full h-full overflow-y-auto pr-2 scrollbar-slim"> <div className="w-full h-full overflow-y-auto pr-2 scrollbar-slim">
@ -215,7 +216,7 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
</table> </table>
</div> </div>
) : ( ) : (
<div style={{ minWidth: minChartWidth > 100 ? `${minChartWidth}px` : '100%', height: '100%' }}> <div style={{ width: '100%', minWidth: minChartWidth > 100 ? `${minChartWidth}px` : '100%', height: '100%' }}>
<ResponsiveContainer width="100%" height="100%" className="focus:outline-none [&_.recharts-wrapper]:outline-none [&_.recharts-surface]:outline-none" style={{ outline: 'none' }}> <ResponsiveContainer width="100%" height="100%" className="focus:outline-none [&_.recharts-wrapper]:outline-none [&_.recharts-surface]:outline-none" style={{ outline: 'none' }}>
{chartType === 3 || chartType === 4 ? ( {chartType === 3 || chartType === 4 ? (
<PieChart margin={{ top: 10, right: 10, left: 10, bottom: 10 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}> <PieChart margin={{ top: 10, right: 10, left: 10, bottom: 10 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
@ -223,6 +224,7 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
contentStyle={{ borderRadius: '8px', border: '1px solid #E2E8F0', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)', fontSize: '14px', fontFamily: 'inherit' }} contentStyle={{ borderRadius: '8px', border: '1px solid #E2E8F0', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)', fontSize: '14px', fontFamily: 'inherit' }}
itemStyle={{ color: '#0F172A', fontWeight: '500' }} itemStyle={{ color: '#0F172A', fontWeight: '500' }}
wrapperStyle={{ zIndex: 1000 }} wrapperStyle={{ zIndex: 1000 }}
allowEscapeViewBox={{ x: true, y: true }}
/> />
<Legend wrapperStyle={{ paddingTop: '20px' }} /> <Legend wrapperStyle={{ paddingTop: '20px' }} />
<Pie <Pie
@ -243,15 +245,15 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
</Pie> </Pie>
</PieChart> </PieChart>
) : chartType === 1 ? ( ) : chartType === 1 ? (
<LineChart data={finalData} margin={{ top: 10, right: 10, left: 0, bottom: 10 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}> <LineChart data={finalData} margin={{ top: 10, right: 30, left: 0, bottom: 10 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
{renderChartContent()} {renderChartContent()}
</LineChart> </LineChart>
) : chartType === 2 ? ( ) : chartType === 2 ? (
<AreaChart data={finalData} margin={{ top: 10, right: 10, left: 0, bottom: 10 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}> <AreaChart data={finalData} margin={{ top: 10, right: 30, left: 0, bottom: 10 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
{renderChartContent()} {renderChartContent()}
</AreaChart> </AreaChart>
) : ( ) : (
<BarChart data={finalData} margin={{ top: 10, right: 10, left: 0, bottom: 10 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}> <BarChart data={finalData} margin={{ top: 10, right: 30, left: 0, bottom: 10 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
{renderChartContent()} {renderChartContent()}
</BarChart> </BarChart>
)} )}

View File

@ -21,6 +21,8 @@ export interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
/** Disable search header filter if set to false */ /** Disable search header filter if set to false */
searchable?: boolean; searchable?: boolean;
onDropdownOpen?: () => void; onDropdownOpen?: () => void;
onSearchChange?: (query: string) => void;
disableLocalSearch?: boolean;
} }
/** Custom searchable select component using React Portal to prevent container clipping. */ /** Custom searchable select component using React Portal to prevent container clipping. */
@ -35,6 +37,8 @@ export function Select({
placeholder, placeholder,
searchable = true, searchable = true,
onDropdownOpen, onDropdownOpen,
onSearchChange,
disableLocalSearch,
...rest ...rest
}: SelectProps) { }: SelectProps) {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@ -52,9 +56,11 @@ export function Select({
const selectedOption = normalizedOptions.find((o) => o.value === currentValue); const selectedOption = normalizedOptions.find((o) => o.value === currentValue);
const selectedOptionLabel = selectedOption ? selectedOption.label : ''; const selectedOptionLabel = selectedOption ? selectedOption.label : '';
const filteredOptions = normalizedOptions.filter((o) => const filteredOptions = disableLocalSearch
o.label.toLowerCase().includes(searchQuery.toLowerCase()) ? normalizedOptions
); : normalizedOptions.filter((o) =>
o.label.toLowerCase().includes(searchQuery.toLowerCase())
);
const updatePosition = useCallback(() => { const updatePosition = useCallback(() => {
if (containerRef.current) { if (containerRef.current) {
@ -180,7 +186,11 @@ export function Select({
<input <input
type="text" type="text"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => {
const val = e.target.value;
setSearchQuery(val);
onSearchChange?.(val);
}}
placeholder="Search options..." placeholder="Search options..."
className="w-full text-xs bg-transparent border-none outline-none text-strong placeholder:text-muted" className="w-full text-xs bg-transparent border-none outline-none text-strong placeholder:text-muted"
autoFocus autoFocus
@ -192,6 +202,7 @@ export function Select({
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
setSearchQuery(''); setSearchQuery('');
onSearchChange?.('');
}} }}
className="text-muted hover:text-strong p-0.5 rounded cursor-pointer" className="text-muted hover:text-strong p-0.5 rounded cursor-pointer"
> >
@ -270,6 +281,7 @@ export interface MultiSelectProps {
className?: string; className?: string;
/** Disable search header filter if set to false */ /** Disable search header filter if set to false */
searchable?: boolean; searchable?: boolean;
onSearchChange?: (query: string) => void;
} }
/** Custom searchable multiselect component using React Portal. */ /** Custom searchable multiselect component using React Portal. */
@ -285,6 +297,7 @@ export function MultiSelect({
disabled, disabled,
placeholder, placeholder,
searchable = true, searchable = true,
onSearchChange,
}: MultiSelectProps) { }: MultiSelectProps) {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
@ -453,7 +466,11 @@ export function MultiSelect({
<input <input
type="text" type="text"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => {
const val = e.target.value;
setSearchQuery(val);
onSearchChange?.(val);
}}
placeholder="Search options..." placeholder="Search options..."
className="w-full text-xs bg-transparent border-none outline-none text-strong placeholder:text-muted" className="w-full text-xs bg-transparent border-none outline-none text-strong placeholder:text-muted"
autoFocus autoFocus
@ -465,6 +482,7 @@ export function MultiSelect({
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
setSearchQuery(''); setSearchQuery('');
onSearchChange?.('');
}} }}
className="text-muted hover:text-strong p-0.5 rounded cursor-pointer" className="text-muted hover:text-strong p-0.5 rounded cursor-pointer"
> >