order details and potential mining did edit and add
This commit is contained in:
parent
e03a5a483c
commit
32ddfff807
@ -9,7 +9,6 @@ import {
|
||||
User,
|
||||
ClipboardList,
|
||||
TrendingUp,
|
||||
Edit3,
|
||||
MapPin,
|
||||
Clock,
|
||||
Phone,
|
||||
@ -25,7 +24,6 @@ export interface CallDetailProps {
|
||||
instanceId: number | string;
|
||||
selectedRow?: Record<string, any>;
|
||||
potentialMiningAction?: ReactNode;
|
||||
placeOrderAction?: ReactNode;
|
||||
refreshKey?: number;
|
||||
onBack?: () => void;
|
||||
onDataLoad?: (data: Record<string, unknown>) => void;
|
||||
@ -35,7 +33,6 @@ export function CallDetail({
|
||||
instanceId,
|
||||
selectedRow,
|
||||
potentialMiningAction,
|
||||
placeOrderAction,
|
||||
refreshKey,
|
||||
onBack,
|
||||
onDataLoad,
|
||||
@ -56,7 +53,7 @@ export function CallDetail({
|
||||
setData(r.data || {});
|
||||
if (onDataLoad) onDataLoad(r.data || {});
|
||||
|
||||
const storeCode = (r.data?.select_store as any)?.store_code || (r.data?.select_store as any)?.code;
|
||||
const storeCode = (r.data?.select_store as any)?.store_code || (r.data?.select_store as any)?.code || (r.data as any)?.store_code || (r.data as any)?.store?.store_code;
|
||||
if (storeCode) {
|
||||
orderBookingClient.request<{ potential: { potential: any[] } }>(
|
||||
'POST',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ArrowUpRight, ArrowDownRight, Pencil, Trash2 } from 'lucide-react';
|
||||
import { Pencil, Trash2 } from 'lucide-react';
|
||||
import { cn } from '../../lib/cn';
|
||||
|
||||
export interface MiningItem {
|
||||
|
||||
@ -16,7 +16,7 @@ export function SmartGridField({
|
||||
value: Record<string, unknown>[];
|
||||
onChange: (val: Record<string, unknown>[]) => void;
|
||||
}) {
|
||||
const [isModalOpen, setIsModalOpen] = useState(true);
|
||||
const isPotentialMining = label.toLowerCase().includes('potential');
|
||||
const formRef = useRef<HTMLDivElement>(null);
|
||||
const [newRow, setNewRow] = useState<Record<string, unknown>>({});
|
||||
const [editingIdx, setEditingIdx] = useState<number | null>(null);
|
||||
@ -44,7 +44,6 @@ export function SmartGridField({
|
||||
const startEdit = (idx: number) => {
|
||||
setEditingIdx(idx);
|
||||
setNewRow({ ...value[idx] });
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const updateNewRowField = (fieldId: string, val: unknown) => {
|
||||
@ -109,6 +108,11 @@ export function SmartGridField({
|
||||
const hasAnyValue = visibleColumns.some(col => newRow[col.id] !== undefined && newRow[col.id] !== null && String(newRow[col.id]).trim() !== '');
|
||||
|
||||
for (const col of visibleColumns) {
|
||||
if (isPotentialMining) {
|
||||
const isReasonOrRemark = col.id.toLowerCase().includes('reason') || col.name.toLowerCase().includes('reason') || col.id.toLowerCase().includes('remark') || col.name.toLowerCase().includes('remark');
|
||||
if (!isReasonOrRemark) continue;
|
||||
}
|
||||
|
||||
const isCategory = col.id === 'product_category' || col.name === 'Product Category' || col.mapped_workflow_field === 'product_category' || getBaseId(col.id) === 'product_category';
|
||||
const isProductName = col.id === 'product_name' || col.name === 'Product Name' || col.mapped_workflow_field === 'product_name' || getBaseId(col.id) === 'product_name';
|
||||
const isBags = col.id === 'bags' || col.name === 'Bags' || col.mapped_workflow_field === 'bags' || getBaseId(col.id) === 'bags' || col.id.toLowerCase().includes('quantity') || col.name.toLowerCase().includes('quantity') || col.mapped_workflow_field?.toLowerCase().includes('quantity');
|
||||
@ -173,13 +177,85 @@ export function SmartGridField({
|
||||
|
||||
// Only close form if we were editing an existing row. For new rows, stay open.
|
||||
if (editingIdx !== null) {
|
||||
setIsModalOpen(false);
|
||||
// Nothing needed here anymore since we removed isModalOpen
|
||||
}
|
||||
setNewRow({});
|
||||
setEditingIdx(null);
|
||||
};
|
||||
|
||||
const isPotentialMining = label.toLowerCase().includes('potential');
|
||||
|
||||
|
||||
const renderFormFields = () => {
|
||||
return visibleColumns.map(col => {
|
||||
const val = newRow[col.id];
|
||||
|
||||
const getBaseId = (id: string) => id.replace(/_\d+$/, '');
|
||||
|
||||
if (isPotentialMining) {
|
||||
const isReasonOrRemark = col.id.toLowerCase().includes('reason') || col.name.toLowerCase().includes('reason') || col.id.toLowerCase().includes('remark') || col.name.toLowerCase().includes('remark');
|
||||
if (!isReasonOrRemark) return null;
|
||||
}
|
||||
|
||||
const catColId = columns.find(c => c.id === 'product_category' || c.name === 'Product Category' || c.mapped_workflow_field === 'product_category' || getBaseId(c.id) === 'product_category')?.id;
|
||||
const isCatSelected = catColId ? !!newRow[catColId] : false;
|
||||
const isProductName = col.id === 'product_name' || col.name === 'Product Name' || col.mapped_workflow_field === 'product_name' || getBaseId(col.id) === 'product_name';
|
||||
const isBags = col.id === 'bags' || col.name === 'Bags' || col.mapped_workflow_field === 'bags' || getBaseId(col.id) === 'bags' || col.id.toLowerCase().includes('quantity') || col.name.toLowerCase().includes('quantity') || col.mapped_workflow_field?.toLowerCase().includes('quantity');
|
||||
const isRequired = col.mandatory || (isCatSelected && (isProductName || isBags));
|
||||
|
||||
if (col.data_type === 'select' || col.data_type === 'multiselect') {
|
||||
const allOpts = col.properties?.options || [];
|
||||
let filteredOpts = allOpts;
|
||||
|
||||
if (col.id === 'product_name' || col.name === 'Product Name' || col.mapped_workflow_field === 'product_name') {
|
||||
const catCol = columns.find(c => c.id === 'product_category' || c.name === 'Product Category' || c.mapped_workflow_field === 'product_category');
|
||||
if (catCol) {
|
||||
const selectedCategory = newRow[catCol.id] as string;
|
||||
if (selectedCategory) {
|
||||
filteredOpts = allOpts.filter(opt => {
|
||||
const labelStr = String(opt.label || opt.value || '');
|
||||
return labelStr.startsWith(selectedCategory);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filteredOpts = allOpts.filter(opt => {
|
||||
if (!opt._raw) return true;
|
||||
for (const [rowKey, rowVal] of Object.entries(newRow)) {
|
||||
if (rowKey === col.id || rowVal == null || rowVal === '') continue;
|
||||
const rawKey = Object.keys(opt._raw).find(rk => rk === rowKey || rk.replace(/_/g, '') === rowKey.replace(/_/g, ''));
|
||||
if (rawKey && String(opt._raw[rawKey]) !== String(rowVal)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
key={col.id}
|
||||
label={col.name}
|
||||
required={isRequired}
|
||||
error={errors[col.id]}
|
||||
value={(val as string) ?? ''}
|
||||
onChange={(e) => updateNewRowField(col.id, e.target.value)}
|
||||
options={[{ value: '', label: 'Select...' }, ...filteredOpts.map((o: any) => ({ value: String(o.value), label: o.label }))]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Input
|
||||
key={col.id}
|
||||
label={col.name}
|
||||
required={isRequired}
|
||||
error={errors[col.id]}
|
||||
type={col.data_type === 'number' ? 'number' : col.data_type === 'email' ? 'email' : 'text'}
|
||||
value={(val as string) ?? ''}
|
||||
onChange={(e) => updateNewRowField(col.id, col.data_type === 'number' ? Number(e.target.value) : e.target.value)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 font-sans border border-border-default rounded-md p-4 bg-slate-50">
|
||||
@ -243,6 +319,40 @@ export function SmartGridField({
|
||||
}
|
||||
const kgsStr = kgsNum > 0 ? ` (${kgsNum} kg)` : '';
|
||||
|
||||
if (editingIdx === i) {
|
||||
return (
|
||||
<div key={i} className="bg-white border border-indigo-200 rounded-xl p-4 shadow-sm flex flex-col gap-4">
|
||||
<h4 className="text-sm font-semibold text-strong flex items-center gap-2">
|
||||
<Pencil size={16} className="text-blue-600" /> Edit Row
|
||||
</h4>
|
||||
<div className="flex flex-col min-w-0 gap-1.5 p-3 bg-slate-50 border border-slate-100 rounded-lg mb-2">
|
||||
<span className="font-bold text-slate-800 text-[14px] truncate">{productName}</span>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{(!isPotentialMining || hasBagsCol) && (
|
||||
<span className="font-extrabold text-indigo-600 text-[13px]">{bags} {isPotentialMining ? 'Kgs' : 'Bags'}{!isPotentialMining ? kgsStr : ''}</span>
|
||||
)}
|
||||
{isPotentialMining && diffVal !== null && !isNaN(diffVal) && diffVal !== 0 && (
|
||||
<span className={`text-[11px] font-bold px-2 py-0.5 rounded-md ${diffVal > 0 ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700'}`}>
|
||||
{Math.abs(diffVal)} {diffVal > 0 ? 'Surplus' : 'Less'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 items-end">
|
||||
{renderFormFields()}
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 mt-2 pt-3 border-t border-slate-100">
|
||||
<Button type="button" variant="ghost" onClick={() => { setEditingIdx(null); setNewRow({}); }}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="button" variant="primary" onClick={submitNewRow}>
|
||||
Save Item
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={i} className="bg-slate-50 border border-slate-200 rounded-xl p-3 shadow-sm flex items-center justify-between">
|
||||
<div className="flex flex-col min-w-0 pr-4 gap-1.5">
|
||||
@ -270,9 +380,11 @@ export function SmartGridField({
|
||||
<button type="button" onClick={() => startEdit(i)} className="text-indigo-600 bg-white border border-indigo-100 hover:bg-indigo-50 p-1.5 rounded-lg transition-colors flex items-center justify-center shadow-sm" title="Edit">
|
||||
<Pencil size={14} />
|
||||
</button>
|
||||
{!isPotentialMining && (
|
||||
<button type="button" onClick={() => removeRow(i)} className="text-red-600 bg-white border border-red-100 hover:bg-red-50 p-1.5 rounded-lg transition-colors flex items-center justify-center shadow-sm" title="Remove">
|
||||
<Trash2 size={14} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -285,95 +397,29 @@ export function SmartGridField({
|
||||
<div
|
||||
ref={formRef}
|
||||
className={`grid transition-[grid-template-rows,opacity,margin] duration-300 ease-in-out ${
|
||||
isModalOpen || editingIdx !== null ? 'grid-rows-[1fr] opacity-100 mt-4' : 'grid-rows-[0fr] opacity-0 mt-0'
|
||||
!isPotentialMining && editingIdx === null ? 'grid-rows-[1fr] opacity-100 mt-4' : 'grid-rows-[0fr] opacity-0 mt-0'
|
||||
}`}
|
||||
>
|
||||
<div className="overflow-hidden">
|
||||
<div className="p-4 border border-border-subtle rounded-xl bg-white shadow-sm">
|
||||
<h4 className="text-sm font-semibold text-strong mb-4 flex items-center gap-2">
|
||||
{editingIdx !== null ? (
|
||||
<><Pencil size={16} className="text-blue-600" /> Edit Row</>
|
||||
) : (
|
||||
<>Add New Item</>
|
||||
)}
|
||||
Add New Item
|
||||
</h4>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 items-end">
|
||||
{visibleColumns.map(col => {
|
||||
const val = newRow[col.id];
|
||||
|
||||
const getBaseId = (id: string) => id.replace(/_\d+$/, '');
|
||||
const catColId = columns.find(c => c.id === 'product_category' || c.name === 'Product Category' || c.mapped_workflow_field === 'product_category' || getBaseId(c.id) === 'product_category')?.id;
|
||||
const isCatSelected = catColId ? !!newRow[catColId] : false;
|
||||
const isProductName = col.id === 'product_name' || col.name === 'Product Name' || col.mapped_workflow_field === 'product_name' || getBaseId(col.id) === 'product_name';
|
||||
const isBags = col.id === 'bags' || col.name === 'Bags' || col.mapped_workflow_field === 'bags' || getBaseId(col.id) === 'bags' || col.id.toLowerCase().includes('quantity') || col.name.toLowerCase().includes('quantity') || col.mapped_workflow_field?.toLowerCase().includes('quantity');
|
||||
const isRequired = col.mandatory || (isCatSelected && (isProductName || isBags));
|
||||
|
||||
if (col.data_type === 'select' || col.data_type === 'multiselect') {
|
||||
const allOpts = col.properties?.options || [];
|
||||
let filteredOpts = allOpts;
|
||||
|
||||
if (col.id === 'product_name' || col.name === 'Product Name' || col.mapped_workflow_field === 'product_name') {
|
||||
const catCol = columns.find(c => c.id === 'product_category' || c.name === 'Product Category' || c.mapped_workflow_field === 'product_category');
|
||||
if (catCol) {
|
||||
const selectedCategory = newRow[catCol.id] as string;
|
||||
if (selectedCategory) {
|
||||
filteredOpts = allOpts.filter(opt => {
|
||||
const labelStr = String(opt.label || opt.value || '');
|
||||
return labelStr.startsWith(selectedCategory);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filteredOpts = allOpts.filter(opt => {
|
||||
if (!opt._raw) return true;
|
||||
for (const [rowKey, rowVal] of Object.entries(newRow)) {
|
||||
if (rowKey === col.id || rowVal == null || rowVal === '') continue;
|
||||
const rawKey = Object.keys(opt._raw).find(rk => rk === rowKey || rk.replace(/_/g, '') === rowKey.replace(/_/g, ''));
|
||||
if (rawKey && String(opt._raw[rawKey]) !== String(rowVal)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
key={col.id}
|
||||
label={col.name}
|
||||
required={isRequired}
|
||||
error={errors[col.id]}
|
||||
value={(val as string) ?? ''}
|
||||
onChange={(e) => updateNewRowField(col.id, e.target.value)}
|
||||
options={[{ value: '', label: 'Select...' }, ...filteredOpts.map((o: any) => ({ value: String(o.value), label: o.label }))]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Input
|
||||
key={col.id}
|
||||
label={col.name}
|
||||
required={isRequired}
|
||||
error={errors[col.id]}
|
||||
type={col.data_type === 'number' ? 'number' : col.data_type === 'email' ? 'email' : 'text'}
|
||||
value={(val as string) ?? ''}
|
||||
onChange={(e) => updateNewRowField(col.id, col.data_type === 'number' ? Number(e.target.value) : e.target.value)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{editingIdx === null && renderFormFields()}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 sm:col-span-2 md:col-span-3 mt-2">
|
||||
<Button type="button" variant="ghost" onClick={() => { setIsModalOpen(false); setEditingIdx(null); setNewRow({}); }}>
|
||||
<Button type="button" variant="ghost" onClick={() => { setNewRow({}); }}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="button" variant="primary" onClick={submitNewRow}>
|
||||
{editingIdx !== null ? "Save Item" : "+ Add to Order"}
|
||||
+ Add to Order
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ export function CallsPage() {
|
||||
setMiningLoading(true);
|
||||
setMiningPrefill(undefined);
|
||||
try {
|
||||
const storeCode = selectedRow?.store_code || selectedRow?.code || selectedRow?.store?.store_code;
|
||||
const storeCode = selectedRow?.store_code || selectedRow?.code || selectedRow?.store?.store_code || selectedRow?.select_store?.store_code || selectedRow?.select_store?.code;
|
||||
|
||||
if (!storeCode) {
|
||||
console.warn("Store code not found on selected row, proceeding anyway.");
|
||||
|
||||
@ -28,7 +28,7 @@ export function OrdersPage() {
|
||||
setMiningLoading(true);
|
||||
setMiningPrefill(undefined);
|
||||
try {
|
||||
const storeCode = selectedRow?.store_code || selectedRow?.code || selectedRow?.store?.store_code;
|
||||
const storeCode = selectedRow?.store_code || selectedRow?.code || selectedRow?.store?.store_code || selectedRow?.select_store?.store_code || selectedRow?.select_store?.code;
|
||||
|
||||
if (!storeCode) {
|
||||
console.warn("Store code not found on selected row, proceeding anyway.");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user