From 61d040d58ed519ae7e204064240c6dfc4ce1e4c9 Mon Sep 17 00:00:00 2001 From: suryacp23 Date: Fri, 10 Jul 2026 17:21:43 +0530 Subject: [PATCH] feature/order order cards structured --- src/App.tsx | 8 +- src/api/config.ts | 2 +- src/components/cards/OrderCard.tsx | 132 +++++++++++++++++++++++++++++ src/components/rv/OrdersView.tsx | 2 + src/components/rv/RecordView.tsx | 43 ++++++---- src/screens/ConsoleLayout.tsx | 68 +++++++++++---- src/styles/styles.css | 1 + vite.config.ts | 2 +- 8 files changed, 219 insertions(+), 39 deletions(-) create mode 100644 src/components/cards/OrderCard.tsx diff --git a/src/App.tsx b/src/App.tsx index 86b4e99..1b2f45f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,19 +10,19 @@ import { DailyLogsPage } from './screens/DailyLogsPage' function App() { return ( - + } /> }> } /> } /> - + } /> } /> - + } /> } /> - + } /> } /> diff --git a/src/api/config.ts b/src/api/config.ts index ea5cadd..68be40f 100644 --- a/src/api/config.ts +++ b/src/api/config.ts @@ -55,7 +55,7 @@ export const ORDER_BOOKING = { }, recordViews: { ORDERS: '1e424561-9a27-44f5-9b68-64d63296d837', - CALLS: 'b4d7a710-24e7-416d-885a-31fd1e727aab', + CALLS: '56b21b46-6d2c-4e10-b5a3-c63d058d0afa', }, detailViews: { ORDERS: '0804c6c3-6cf9-4050-94bb-fa48dd5de87d', diff --git a/src/components/cards/OrderCard.tsx b/src/components/cards/OrderCard.tsx new file mode 100644 index 0000000..83e7ddb --- /dev/null +++ b/src/components/cards/OrderCard.tsx @@ -0,0 +1,132 @@ +import { formatValue } from '../../lib/format'; +import { Badge } from '../reusable/Badge'; +import { CheckCircle2Icon, Factory, UserIcon } from 'lucide-react'; + +export function OrderCard({ row, fields }: { row: Record; fields: any[] }) { + const orderIdStr = row.order_id; + + // Extract store lookup object if it exists + const storeObj = (row.select_store || row.store) as Record | null; + const storeVal = formatValue(storeObj?.business_name || '—'); + const routeVal = formatValue(storeObj?.route_name || '—'); + const distributorVal = formatValue(storeObj?.distributor_name || '—'); + + // Extract user/performed_by object + const userKey = Object.keys(row).find(k => k.includes('user_id') || k.includes('created_by')); + const userObj = userKey ? row[userKey] as Record : null; + const userVal = formatValue(userObj?.name || userObj?.user_name || '—'); + const userEmail = userObj?.email ? String(userObj.email) : ''; + + const dateVal = row.date_of_order ? formatValue(row.date_of_order) : '—'; + + const stateName = String(row.current_state_name || ''); + const isProductive = stateName.toLowerCase().includes('productive') || stateName.toLowerCase().includes('closed'); + const productiveLabel = stateName || 'Pending'; + + // Order Details Grid + const gridField = fields.find(f => f.data_type === 'grid' || f.field_key === 'order_details'); + const gridValRaw = gridField ? row[gridField.field_key] : row.order_details; + const gridVal = Array.isArray(gridValRaw) ? gridValRaw : []; + + return ( +
+ + {/* Top Header Section */} +
+
+
+ Order ID + {orderIdStr} +
+ + {isProductive && } + {productiveLabel} + +
+ +
+
+ Date + {dateVal} +
+
+ Store & Route + + {storeVal} ({routeVal}) + +
+
+
+ + {/* Middle Section: Distributor & User */} +
+
+
+ +
+
+ Distributor + {distributorVal} +
+
+ +
+ +
+
+ Placed by + {userVal} +
+
+ +
+
+
+ + {/* Bottom Section: Order Items */} + {gridVal.length > 0 && ( +
+
+

+ Order Items + + {gridVal.length} + +

+
+ +
+ {gridVal.map((item, i) => { + const productName = formatValue(item.product_name || item.product_category || 'Unknown Product'); + const sku = formatValue(item.sku_code || item.sku || 'N/A'); + const bags = formatValue(item.bags || item.total_bags || item.quantity || 0); + + return ( +
+
+
+ + {sku} + +
+ + {productName} + +
+
+ + {bags} + + + bags + +
+
+ ); + })} +
+
+ )} +
+ ); +} diff --git a/src/components/rv/OrdersView.tsx b/src/components/rv/OrdersView.tsx index b8f061e..398636a 100644 --- a/src/components/rv/OrdersView.tsx +++ b/src/components/rv/OrdersView.tsx @@ -1,6 +1,7 @@ import { orderBookingClient } from '../../api/clients'; import { ORDER_BOOKING } from '../../api/config'; import { RecordView } from './RecordView'; +import { OrderCard } from '../cards/OrderCard'; export interface WiredRecordViewProps { onRowClick?: (row: Record, index: number) => void; @@ -22,6 +23,7 @@ export function OrdersView({ onRowClick, pageSize, headerActions, rowActions, re headerActions={headerActions} rowActions={rowActions} refreshKey={refreshKey} + renderItem={(row, fields) => } /> ); } diff --git a/src/components/rv/RecordView.tsx b/src/components/rv/RecordView.tsx index 9c5b61c..6995f47 100644 --- a/src/components/rv/RecordView.tsx +++ b/src/components/rv/RecordView.tsx @@ -32,6 +32,8 @@ export interface RecordViewProps { rowActions?: (row: Record) => React.ReactNode; /** Pass a new value to trigger a refresh. */ refreshKey?: number; + /** Custom render function for the entire row card body */ + renderItem?: (row: Record, fields: RecordViewField[]) => React.ReactNode; } /** @@ -50,6 +52,7 @@ export function RecordView({ headerActions, rowActions, refreshKey, + renderItem, }: RecordViewProps) { const [page, setPage] = useState(1); const [search, setSearch] = useState(''); @@ -183,30 +186,34 @@ export function RecordView({ key={rowKey ? rowKey(row, i) : String(row.instance_id ?? i)} onClick={onRowClick ? () => onRowClick(row, i) : undefined} className={cn( - 'rounded-xl border border-border-subtle bg-card p-4 shadow-sm transition-transform duration-150', + 'transition-transform duration-150 relative w-full', + !renderItem && 'rounded-xl border border-border-subtle bg-card p-4 shadow-sm', onRowClick && 'cursor-pointer active:scale-[0.99]', )} > -
-
- {primary && ( -

- {formatValue(row[primary.field_key])} -

- )} - {secondary.length > 0 && ( -
- {secondary.map((f) => ( -

- {f.output_label}: {formatValue(row[f.field_key])} +

+
+ {renderItem ? ( + renderItem(row, fields) + ) : ( + <> + {primary && ( +

+ {formatValue(row[primary.field_key])}

- ))} -
+ )} + {secondary.length > 0 && ( +
+ {secondary.map((f) => ( +

+ {f.output_label}: {formatValue(row[f.field_key])} +

+ ))} +
+ )} + )}
- {onRowClick && ( - - )}
{rowActions && (
document.documentElement.classList.contains('dark')); + + useEffect(() => { + const saved = localStorage.getItem('theme'); + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + if (saved === 'dark' || (!saved && prefersDark)) { + document.documentElement.classList.add('dark'); + setIsDark(true); + } else { + document.documentElement.classList.remove('dark'); + setIsDark(false); + } + }, []); + + const toggleTheme = () => { + if (isDark) { + document.documentElement.classList.remove('dark'); + localStorage.setItem('theme', 'light'); + setIsDark(false); + } else { + document.documentElement.classList.add('dark'); + localStorage.setItem('theme', 'dark'); + setIsDark(true); + } + }; // A 401 from any workflow client bounces back to login. useEffect(() => { @@ -24,23 +50,35 @@ export function ConsoleLayout() { if (!authed) return ; return ( -
+
-
+
Krishna Sales Sandbox {APP_ID}{user?.name ? ` · ${user.name}` : ''}
- + +
+ + + +
diff --git a/src/styles/styles.css b/src/styles/styles.css index e3d6ef0..9230be8 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -8,6 +8,7 @@ ============================================================ */ @import url('https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400;14..32,500;14..32,600;14..32,700;14..32,800&family=Inter+Tight:wght@500;600;700;800&display=swap'); @import 'tailwindcss'; +@custom-variant dark (&:where(.dark, .dark *)); @import './tokens/colors.css'; @import './tokens/typography.css'; @import './tokens/spacing.css'; diff --git a/vite.config.ts b/vite.config.ts index 24a9aec..3a9b14f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,7 +4,7 @@ import tailwindcss from '@tailwindcss/vite' import { VitePWA } from 'vite-plugin-pwa' // https://vite.dev/config/ -const base = process.env.VITE_BASE_URL ?? '/' +const base = process.env.VITE_BASE_URL ?? '/krishna_sales_mobile' export default defineConfig({ base,