fix:ui of the order detail form
This commit is contained in:
parent
628548a509
commit
4190e053e0
@ -1,8 +1,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useRef } from 'react';
|
||||||
import { Button } from '../../buttons/Button';
|
import { Button } from '../../buttons/Button';
|
||||||
import { Select } from '../../reusable/Select';
|
import { Select } from '../../reusable/Select';
|
||||||
import { Input } from '../../reusable/Input';
|
import { Input } from '../../reusable/Input';
|
||||||
import { Modal } from '../../reusable/Modal';
|
|
||||||
import { Trash2, Pencil } from 'lucide-react';
|
import { Trash2, Pencil } from 'lucide-react';
|
||||||
import type { FormScreenField } from '../../../api/types';
|
import type { FormScreenField } from '../../../api/types';
|
||||||
|
|
||||||
@ -18,6 +17,7 @@ export function SmartGridField({
|
|||||||
onChange: (val: Record<string, unknown>[]) => void;
|
onChange: (val: Record<string, unknown>[]) => void;
|
||||||
}) {
|
}) {
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const formRef = useRef<HTMLDivElement>(null);
|
||||||
const [newRow, setNewRow] = useState<Record<string, unknown>>({});
|
const [newRow, setNewRow] = useState<Record<string, unknown>>({});
|
||||||
const [editingIdx, setEditingIdx] = useState<number | null>(null);
|
const [editingIdx, setEditingIdx] = useState<number | null>(null);
|
||||||
|
|
||||||
@ -49,6 +49,9 @@ export function SmartGridField({
|
|||||||
setEditingIdx(null);
|
setEditingIdx(null);
|
||||||
setNewRow({});
|
setNewRow({});
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
formRef.current?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
}, 150);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateNewRowField = (fieldId: string, val: unknown) => {
|
const updateNewRowField = (fieldId: string, val: unknown) => {
|
||||||
@ -140,7 +143,11 @@ export function SmartGridField({
|
|||||||
onChange([...value, newRow]);
|
onChange([...value, newRow]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setIsModalOpen(false);
|
|
||||||
|
// Only close form if we were editing an existing row. For new rows, stay open.
|
||||||
|
if (editingIdx !== null) {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
}
|
||||||
setNewRow({});
|
setNewRow({});
|
||||||
setEditingIdx(null);
|
setEditingIdx(null);
|
||||||
};
|
};
|
||||||
@ -210,80 +217,100 @@ export function SmartGridField({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mt-4 p-4 border border-border-subtle rounded-xl bg-white shadow-sm">
|
<div className="flex justify-end mt-2">
|
||||||
<h4 className="text-sm font-semibold text-strong mb-4 flex items-center gap-2">
|
<Button
|
||||||
{editingIdx !== null ? (
|
type="button"
|
||||||
<><Pencil size={16} className="text-blue-600" /> Edit Row</>
|
variant="primary"
|
||||||
) : (
|
size="sm"
|
||||||
<>Add New Item</>
|
onClick={startAdd}
|
||||||
)}
|
className={`transition-all duration-300 ease-in-out font-bold text-lg leading-none ${isModalOpen || editingIdx !== null ? 'opacity-0 pointer-events-none scale-95 w-0 h-0 p-0 m-0 overflow-hidden' : 'opacity-100 scale-100 rounded-full w-10 h-10 flex items-center justify-center shadow-md'}`}
|
||||||
</h4>
|
title="Add Row"
|
||||||
<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];
|
</Button>
|
||||||
if (col.data_type === 'select' || col.data_type === 'multiselect') {
|
</div>
|
||||||
const allOpts = col.properties?.options || [];
|
|
||||||
let filteredOpts = allOpts;
|
<div
|
||||||
|
ref={formRef}
|
||||||
if (col.id === 'product_name' || col.name === 'Product Name' || col.mapped_workflow_field === 'product_name') {
|
className={`grid transition-[grid-template-rows,opacity,margin] duration-300 ease-in-out ${
|
||||||
const catCol = columns.find(c => c.id === 'product_category' || c.name === 'Product Category' || c.mapped_workflow_field === 'product_category');
|
isModalOpen || editingIdx !== null ? 'grid-rows-[1fr] opacity-100 mt-4' : 'grid-rows-[0fr] opacity-0 mt-0'
|
||||||
if (catCol) {
|
}`}
|
||||||
const selectedCategory = newRow[catCol.id] as string;
|
>
|
||||||
if (selectedCategory) {
|
<div className="overflow-hidden">
|
||||||
filteredOpts = allOpts.filter(opt => {
|
<div className="p-4 border border-border-subtle rounded-xl bg-white shadow-sm">
|
||||||
const labelStr = String(opt.label || opt.value || '');
|
<h4 className="text-sm font-semibold text-strong mb-4 flex items-center gap-2">
|
||||||
return labelStr.startsWith(selectedCategory);
|
{editingIdx !== null ? (
|
||||||
});
|
<><Pencil size={16} className="text-blue-600" /> Edit Row</>
|
||||||
}
|
) : (
|
||||||
}
|
<>Add New Item</>
|
||||||
} else {
|
)}
|
||||||
filteredOpts = allOpts.filter(opt => {
|
</h4>
|
||||||
if (!opt._raw) return true;
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 items-end">
|
||||||
for (const [rowKey, rowVal] of Object.entries(newRow)) {
|
{visibleColumns.map(col => {
|
||||||
if (rowKey === col.id || rowVal == null || rowVal === '') continue;
|
const val = newRow[col.id];
|
||||||
const rawKey = Object.keys(opt._raw).find(rk => rk === rowKey || rk.replace(/_/g, '') === rowKey.replace(/_/g, ''));
|
if (col.data_type === 'select' || col.data_type === 'multiselect') {
|
||||||
if (rawKey && String(opt._raw[rawKey]) !== String(rowVal)) {
|
const allOpts = col.properties?.options || [];
|
||||||
return false;
|
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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
} 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={col.mandatory}
|
||||||
|
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 (
|
return (
|
||||||
<Select
|
<Input
|
||||||
key={col.id}
|
key={col.id}
|
||||||
label={col.name}
|
label={col.name}
|
||||||
required={col.mandatory}
|
required={col.mandatory}
|
||||||
|
type={col.data_type === 'number' ? 'number' : col.data_type === 'email' ? 'email' : 'text'}
|
||||||
value={(val as string) ?? ''}
|
value={(val as string) ?? ''}
|
||||||
onChange={(e) => updateNewRowField(col.id, e.target.value)}
|
onChange={(e) => updateNewRowField(col.id, col.data_type === 'number' ? Number(e.target.value) : e.target.value)}
|
||||||
options={[{ value: '', label: 'Select...' }, ...filteredOpts.map((o: any) => ({ value: String(o.value), label: o.label }))]}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
})}
|
||||||
return (
|
|
||||||
<Input
|
<div className="flex justify-end gap-2 sm:col-span-2 md:col-span-3 mt-2">
|
||||||
key={col.id}
|
<Button type="button" variant="ghost" onClick={() => { setIsModalOpen(false); setEditingIdx(null); setNewRow({}); }}>
|
||||||
label={col.name}
|
|
||||||
required={col.mandatory}
|
|
||||||
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)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
<div className="flex justify-end gap-2 sm:col-span-2 md:col-span-3 mt-2">
|
|
||||||
{editingIdx !== null && (
|
|
||||||
<Button type="button" variant="ghost" onClick={() => { setEditingIdx(null); setNewRow({}); }}>
|
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
<Button type="button" variant="primary" onClick={submitNewRow}>
|
||||||
<Button type="button" variant="primary" onClick={submitNewRow}>
|
{editingIdx !== null ? "Save Item" : "+ Add to Order"}
|
||||||
{editingIdx !== null ? "Save Item" : "+ Add to Order"}
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user