From 3eb445d5065a49e2f0b24274547692848aaac782 Mon Sep 17 00:00:00 2001 From: suryacp23 Date: Tue, 14 Jul 2026 15:40:33 +0530 Subject: [PATCH] fixed the form table smart grid --- src/components/buttons/Button.tsx | 9 +- src/components/forms/DynamicForm.tsx | 20 +- .../forms/fields/SmartGridField.tsx | 218 ++++++++++++------ src/components/reusable/Modal.tsx | 2 +- src/screens/CallsPage.tsx | 45 ++-- src/screens/OrdersPage.tsx | 10 - 6 files changed, 194 insertions(+), 110 deletions(-) diff --git a/src/components/buttons/Button.tsx b/src/components/buttons/Button.tsx index 81594ea..44b5df6 100644 --- a/src/components/buttons/Button.tsx +++ b/src/components/buttons/Button.tsx @@ -2,8 +2,8 @@ import type { ButtonHTMLAttributes, ReactNode } from 'react'; import { cn } from '../../lib/cn'; import './style.css' -export type ButtonVariant = 'primary' | 'navy' | 'secondary' | 'ghost' | 'danger'|'outline'; -export type ButtonSize = 'sm' | 'md' | 'lg'; +export type ButtonVariant = 'primary' | 'navy' | 'secondary' | 'ghost' | 'danger' | 'outline'; +export type ButtonSize = 'sm' | 'md' | 'lg' | 'fab'; export interface ButtonProps extends ButtonHTMLAttributes { /** Visual style. @default "primary" */ @@ -20,6 +20,7 @@ const SIZES: Record = { sm: 'h-[34px] px-3.5 text-[13px] rounded-sm', md: 'h-[42px] px-[18px] text-base rounded-md', lg: 'h-[50px] px-6 text-md rounded-md', + fab: 'h-[56px] w-[56px] text-sm rounded-full', }; const VARIANTS: Record = { @@ -28,7 +29,7 @@ const VARIANTS: Record = { secondary: 'bg-card text-strong border border-border-default shadow-xs', ghost: 'bg-transparent text-body border border-transparent', danger: 'bg-ruby-600 text-white border border-transparent shadow-sm', - outline:'z-btn-outline' + outline: 'z-btn-outline' }; /** @@ -54,7 +55,7 @@ export function Button({ 'disabled:opacity-50 disabled:cursor-not-allowed disabled:active:scale-100', SIZES[size], VARIANTS[variant], - full ? 'w-full' : 'w-auto', + full ? 'w-full' : '', className, )} {...rest} diff --git a/src/components/forms/DynamicForm.tsx b/src/components/forms/DynamicForm.tsx index d1c7031..04b4d06 100644 --- a/src/components/forms/DynamicForm.tsx +++ b/src/components/forms/DynamicForm.tsx @@ -17,6 +17,7 @@ import { WfLookupField, RadioField, } from './fields'; +import { ORDER_BOOKING } from '../../api/config'; export interface DynamicFormProps { client: ZinoClient; @@ -172,12 +173,25 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId: } let pending = [...chainQueue]; - const newActivities = (schema.activity_chain || []).filter( - a => a.activity_uid !== currentActivityId + let chainSource = (res as any).activity_chain || schema.activity_chain || []; + + // Centralized activity chaining logic + if (currentActivityId === ORDER_BOOKING.activities.PRODUCTIVITY_OF_VISIT.uid) { + const actionValue = String(payload[ORDER_BOOKING.activities.PRODUCTIVITY_OF_VISIT.fields.action] || '').toLowerCase().trim(); + const normalized = actionValue.replace(/[\s_]+/g, ''); + if (normalized === 'order') { + chainSource = [{ activity_uid: ORDER_BOOKING.activities.PLACE_ORDER.uid, activity_name: 'Place Order' }]; + } else if (normalized === 'noorder') { + chainSource = [{ activity_uid: ORDER_BOOKING.activities.POTENTIAL_MINING.uid, activity_name: 'Potential Mining' }]; + } + } + + const newActivities = chainSource.filter( + (a: any) => a.activity_uid !== currentActivityId ); // Remove any existing occurrences from pending to avoid duplicates - pending = pending.filter(p => !newActivities.some(n => n.activity_uid === p.activity_uid)); + pending = pending.filter(p => !newActivities.some((n: any) => n.activity_uid === p.activity_uid)); // Prepend the new activities for depth-first execution (nested chaining) pending = [...newActivities, ...pending]; diff --git a/src/components/forms/fields/SmartGridField.tsx b/src/components/forms/fields/SmartGridField.tsx index 8c45803..21897af 100644 --- a/src/components/forms/fields/SmartGridField.tsx +++ b/src/components/forms/fields/SmartGridField.tsx @@ -1,6 +1,9 @@ +import { useState } from 'react'; import { Button } from '../../buttons/Button'; import { Select } from '../../reusable/Select'; import { Input } from '../../reusable/Input'; +import { Modal } from '../../reusable/Modal'; +import { Trash2, Pencil } from 'lucide-react'; import type { FormScreenField } from '../../../api/types'; export function SmartGridField({ @@ -14,7 +17,14 @@ export function SmartGridField({ value: Record[]; onChange: (val: Record[]) => void; }) { - const addRow = () => onChange([...value, {}]); + const [isModalOpen, setIsModalOpen] = useState(false); + const [newRow, setNewRow] = useState>({}); + const [editingIdx, setEditingIdx] = useState(null); + + const visibleColumns = columns.filter(c => { + const normalized = c.id.toLowerCase().replace(/[^a-z]/g, ''); + return ['productcategory', 'productname', 'bags'].includes(normalized); + }); const removeRow = (idx: number) => { const next = [...value]; @@ -22,9 +32,20 @@ export function SmartGridField({ onChange(next); }; - const updateRow = (idx: number, fieldId: string, val: unknown) => { - const next = [...value]; - let row = { ...next[idx], [fieldId]: val }; + const startEdit = (idx: number) => { + setEditingIdx(idx); + setNewRow({ ...value[idx] }); + setIsModalOpen(true); + }; + + const startAdd = () => { + setEditingIdx(null); + setNewRow({}); + setIsModalOpen(true); + }; + + const updateNewRowField = (fieldId: string, val: unknown) => { + let row = { ...newRow, [fieldId]: val }; const colDef = columns.find(c => c.id === fieldId); @@ -42,7 +63,6 @@ export function SmartGridField({ const option = colDef.properties?.options?.find(o => String(o.value) === String(val)); if (option && option._raw) { for (const key of Object.keys(option._raw)) { - // Match column ID with or without underscores (e.g., brcode <-> br_code) const targetCol = columns.find(c => c.id === key || c.id.replace(/_/g, '') === key.replace(/_/g, '')); if (targetCol && targetCol.id !== fieldId) { row[targetCol.id] = option._raw[key]; @@ -56,90 +76,136 @@ export function SmartGridField({ const bagsVal = Number(row.bags) || 0; row.row_kgs = skuVal * bagsVal; - next[idx] = row; - onChange(next); + setNewRow(row); + }; + + const submitNewRow = () => { + if (editingIdx !== null) { + const next = [...value]; + next[editingIdx] = newRow; + onChange(next); + } else { + onChange([...value, newRow]); + } + setIsModalOpen(false); + setNewRow({}); + setEditingIdx(null); }; return (
{label} + {value.length === 0 ? ( No rows added. ) : ( -
- {value.map((row, i) => ( -
-
- -
- Row {i + 1} -
- {columns.map(col => { - const val = row[col.id]; - if (col.data_type === 'select' || col.data_type === 'multiselect') { - // Cascade filter options - const allOpts = col.properties?.options || []; - let filteredOpts = allOpts; - - // Specific logic for product_name depending on product_category - if (col.id === 'product_name' || col.name === 'Product Name') { - const catCol = columns.find(c => c.id === 'product_category' || c.name === 'Product Category'); - if (catCol) { - const selectedCategory = row[catCol.id] as string; - if (selectedCategory) { - filteredOpts = allOpts.filter(opt => { - const labelStr = String(opt.label || opt.value || ''); - return labelStr.startsWith(selectedCategory); - }); +
+ + + + {visibleColumns.map(col => ( + + ))} + + + + + {value.map((row, i) => ( + + {visibleColumns.map(col => ( + + ))} + + + ))} + +
+ {col.name} +
+ {(() => { + const val = row[col.id]; + if (col.data_type === 'select' || col.data_type === 'multiselect') { + const opt = col.properties?.options?.find(o => String(o.value) === String(val)); + return opt ? opt.label : String(val ?? '-'); } - } - } else { - // Generic _raw cascading for other fields just in case - filteredOpts = allOpts.filter(opt => { - if (!opt._raw) return true; - for (const [rowKey, rowVal] of Object.entries(row)) { - 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 ( - updateRow(i, col.id, col.data_type === 'number' ? Number(e.target.value) : e.target.value)} - /> - ); - })} - - - ))} + return String(val ?? '-'); + })()} + + + +
)} - + + setIsModalOpen(false)} title={editingIdx !== null ? "Edit Row" : "Add Row"} width="sm"> +
{ e.preventDefault(); e.stopPropagation(); submitNewRow(); }} className="flex flex-col gap-4"> + {visibleColumns.map(col => { + const val = newRow[col.id]; + 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') { + const catCol = columns.find(c => c.id === 'product_category' || c.name === '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 ( + updateNewRowField(col.id, col.data_type === 'number' ? Number(e.target.value) : e.target.value)} + /> + ); + })} +
+ + +
+
+
); } diff --git a/src/components/reusable/Modal.tsx b/src/components/reusable/Modal.tsx index 1f09602..bef9884 100644 --- a/src/components/reusable/Modal.tsx +++ b/src/components/reusable/Modal.tsx @@ -52,7 +52,7 @@ export function Modal({ open, onClose, title, subtitle, width = 'md', actions, c role="dialog" aria-modal="true" className={cn( - 'w-full bg-card rounded-t-2xl sm:rounded-lg border border-border-subtle shadow-lg sm:my-auto', + 'relative w-full bg-card rounded-t-2xl sm:rounded-lg border border-border-subtle shadow-lg sm:my-auto', 'flex flex-col max-h-[92vh] sm:max-h-[85vh]', WIDTH[width], )} diff --git a/src/screens/CallsPage.tsx b/src/screens/CallsPage.tsx index 0bd6938..620ee1d 100644 --- a/src/screens/CallsPage.tsx +++ b/src/screens/CallsPage.tsx @@ -16,15 +16,16 @@ export function CallsPage() { const [isCreating, setIsCreating] = useState(false); const [refreshKey, setRefreshKey] = useState(0); const [activeActivity, setActiveActivity] = useState<{ id: string; name: string } | null>(null); + const [isFabOpen, setIsFabOpen] = useState(false); return ( <> - { const id = row.instance_id as number | string | undefined; if (id != null) navigate(`/calls/${id}`); - }} + }} headerActions={ - - - } > - {instanceId != null && } + {instanceId != null && ( + <> + +
+ {isFabOpen && ( +
+ + +
+ )} + +
+ + )} navigate(`/orders`)} title={instanceId != null ? `Order #${instanceId}` : undefined} width="lg" - actions={ - <> - - - - } > {instanceId != null && }