From 1ac818d0070e4ed4bbab06e68afb3d117fafa4fa Mon Sep 17 00:00:00 2001 From: vyshnav mv Date: Wed, 31 Dec 2025 02:00:36 -0800 Subject: [PATCH 01/14] Feat: visual layout for the table , can edit and drag and zoom --- pos/src/components/LayoutView.tsx | 669 ++++++++++++++++++++++++++++++ pos/src/lib/table-api.ts | 49 ++- pos/src/lib/utils.ts | 29 +- pos/src/pages/Table.tsx | 170 ++++---- 4 files changed, 825 insertions(+), 92 deletions(-) create mode 100644 pos/src/components/LayoutView.tsx diff --git a/pos/src/components/LayoutView.tsx b/pos/src/components/LayoutView.tsx new file mode 100644 index 0000000..1210ba2 --- /dev/null +++ b/pos/src/components/LayoutView.tsx @@ -0,0 +1,669 @@ +import React, { useState, useRef, useMemo, useEffect, useCallback } from 'react'; +import { CreditCard as Edit3, Save, Grid3x3 as Grid3X3, ZoomIn, ZoomOut, RotateCcw, X, Users, Move} from 'lucide-react'; +import { cn, formatInvoiceTime } from '../lib/utils'; +import { Table, updateTableLayout } from '../lib/table-api'; +import { getTableOrder, POSInvoice } from '../lib/order-api'; +import { Button } from './ui'; +// import { showToast } from './ui/toast'; + + + +interface Props { + selectedRoom: string; + tables: Table[]; + onBackToGrid: () => void; + onRefresh?: () => void; // Add refresh callback +} + +const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRefresh }) => { + const [isEditMode, setIsEditMode] = useState(false); + + // Local state for optimistic updates + const [localLayouts, setLocalLayouts] = useState>>({}); + const [draggedTable, setDraggedTable] = useState(null); + const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }); + const [selectedTable, setSelectedTable] = useState(null); + const [selectedTableOrder, setSelectedTableOrder] = useState(null); + const canvasRef = useRef(null); + const [zoom, setZoom] = useState(1); + const [panOffset, setPanOffset] = useState({ x: 0, y: 0 }); + const [isPanning, setIsPanning] = useState(false); + const [panStart, setPanStart] = useState({ x: 0, y: 0 }); + + // Save to local storage effect removed + + + // Merge props.tables with saved positions + const tablesWithPosition = useMemo(() => { + return tables.map((table, index) => { + const local = localLayouts[table.name] || {}; + + // Use local overrides, then backend fields, then grid defaults + const x = local.layout_x ?? table.layout_x ?? (100 + (index % 5) * 150); + const y = local.layout_y ?? table.layout_y ?? (100 + Math.floor(index / 5) * 150); + + return { + ...table, + x, + y, + table_shape: local.table_shape ?? table.table_shape, + no_of_seats: local.no_of_seats ?? table.no_of_seats, + }; + }); + }, [tables, localLayouts]); + + // Calculate table dimensions based on capacity and shape + const getTableDimensions = (shape: string, capacity: number = 4) => { + // Dynamic sizing: minimum 60px, scales up by 10px per person, max 250px + const size = Math.max(60, Math.min(250, 60 + (capacity * 10))); + + const normalizedShape = shape?.toLowerCase() || 'rectangle'; + + switch (normalizedShape) { + case 'circle': + return { width: size, height: size }; + case 'square': + return { width: size, height: size }; + case 'rectangle': + default: + return { width: size * 1.5, height: size }; + } + }; + + // Helper to zoom around a specific point (screen coordinates) + const zoomToPoint = useCallback((newZoom: number, pivotX: number, pivotY: number) => { + // 1. Calculate the point in World coordinates before zoom + // worldX = (screenX - panX) / oldZoom + const worldPointX = (pivotX - panOffset.x) / zoom; + const worldPointY = (pivotY - panOffset.y) / zoom; + + // 2. Update Zoom + const clampedZoom = Math.max(0.3, Math.min(3, newZoom)); + setZoom(clampedZoom); + + // 3. Calculate new Pan to keep the World point at the same Screen position + // screenX = newPanX + worldX * newZoom + // => newPanX = screenX - worldX * newZoom + setPanOffset({ + x: pivotX - worldPointX * clampedZoom, + y: pivotY - worldPointY * clampedZoom + }); + }, [zoom, panOffset]); + + // Zoom functionality (buttons zoom to center) + const handleZoomIn = () => { + if (!canvasRef.current) return; + const rect = canvasRef.current.getBoundingClientRect(); + const centerX = rect.width / 2; + const centerY = rect.height / 2; + zoomToPoint(zoom + 0.2, centerX, centerY); + }; + + const handleZoomOut = () => { + if (!canvasRef.current) return; + const rect = canvasRef.current.getBoundingClientRect(); + const centerX = rect.width / 2; + const centerY = rect.height / 2; + zoomToPoint(zoom - 0.2, centerX, centerY); + }; + + const handleResetZoom = () => { + setZoom(1); + setPanOffset({ x: 0, y: 0 }); + }; + + // Mouse wheel zoom (zooms to cursor) + const handleWheel = useCallback((e: WheelEvent) => { + e.preventDefault(); + e.stopPropagation(); + + if (!canvasRef.current) return; + const rect = canvasRef.current.getBoundingClientRect(); + const mouseX = e.clientX - rect.left; + const mouseY = e.clientY - rect.top; + + const delta = e.deltaY > 0 ? -0.1 : 0.1; + zoomToPoint(zoom + delta, mouseX, mouseY); + }, [zoom, zoomToPoint]); + + // Use ref to attach non-passive listener for proper preventDefault + useEffect(() => { + const canvas = canvasRef.current; + if (canvas) { + canvas.addEventListener('wheel', handleWheel, { passive: false }); + } + return () => { + if (canvas) { + canvas.removeEventListener('wheel', handleWheel); + } + }; + }, [handleWheel]); + + // Pan functionality + const handleCanvasMouseDown = (e: React.MouseEvent) => { + // Start panning if we clicked on the background (wrapper or outer container) + // Tables stop propagation, so if we get here, it's safe to pan + setIsPanning(true); + setPanStart({ x: e.clientX - panOffset.x, y: e.clientY - panOffset.y }); + }; + + const handleCanvasMouseMove = (e: React.MouseEvent) => { + if (isPanning) { + setPanOffset({ + x: e.clientX - panStart.x, + y: e.clientY - panStart.y + }); + return; + } + + // Drag functionality + if (!draggedTable || !isEditMode || !canvasRef.current) return; + + const canvasRect = canvasRef.current.getBoundingClientRect(); + + // We subtract panOffset and divide by zoom to get back to "world" coordinates + const mouseX = (e.clientX - canvasRect.left - panOffset.x) / zoom; + const mouseY = (e.clientY - canvasRect.top - panOffset.y) / zoom; + + const newX = mouseX - dragOffset.x; + const newY = mouseY - dragOffset.y; + + // Update local state for immediate feedback + setLocalLayouts(prev => ({ + ...prev, + [draggedTable]: { + ...(prev[draggedTable] || {}), + layout_x: newX, + layout_y: newY, + } + })); + }; + + const persistTableUpdate = (tableName: string, changes: Partial) => { + const table = tablesWithPosition.find(t => t.name === tableName); + if (!table) return Promise.reject("Table not found"); + + // AND ensure we fallback to backend values for undefined fields. + const payload = { + layout_x: changes.layout_x ?? table.x, + layout_y: changes.layout_y ?? table.y, + table_shape: changes.table_shape ?? table.table_shape, + no_of_seats: changes.no_of_seats ?? table.no_of_seats, + minimum_seating: table.minimum_seating // preserve existing if not changing + }; + return updateTableLayout(tableName, payload); + }; + + const handleCanvasMouseUp = () => { + if (draggedTable && isEditMode) { + const table = tablesWithPosition.find(t => t.name === draggedTable); + if (table) { + // table.x and table.y are already updated via local state during drag + persistTableUpdate(table.name, { + layout_x: table.x, + layout_y: table.y + }).catch(err => console.error("Failed to save layout", err)); + } + } + + setIsPanning(false); + setDraggedTable(null); + setDragOffset({ x: 0, y: 0 }); + }; + + const getTableStatusColor = (occupied: number) => { + return occupied + ? 'bg-amber-100 border-amber-300 text-amber-900 shadow-sm' + : 'bg-emerald-50 border-emerald-200 text-emerald-800 hover:shadow-md'; + }; + + const handleMouseDown = (e: React.MouseEvent, table: typeof tablesWithPosition[0]) => { + e.stopPropagation(); + + setSelectedTable(table.name); + + if (table.occupied) { + getTableOrder(table.name).then(res => { + setSelectedTableOrder(res.message); + }).catch(console.error); + } else { + setSelectedTableOrder(null); + } + + if (!isEditMode) return; + + const canvasRect = canvasRef.current?.getBoundingClientRect(); + if (!canvasRect) return; + + const mouseXWorld = (e.clientX - canvasRect.left - panOffset.x) / zoom; + const mouseYWorld = (e.clientY - canvasRect.top - panOffset.y) / zoom; + + setDraggedTable(table.name); + setDragOffset({ + x: mouseXWorld - table.x, + y: mouseYWorld - table.y + }); + }; + + const handleShapeChange = (e: React.MouseEvent, table: typeof tablesWithPosition[0]) => { + e.stopPropagation(); + const shapes: ('Circle' | 'Square' | 'Rectangle')[] = ['Circle', 'Square', 'Rectangle']; + const currentShape = table.table_shape || 'Rectangle'; + // Find current shape case-insensitively + const currentIndex = shapes.findIndex(s => s.toLowerCase() === currentShape.toLowerCase()); + const nextShape = shapes[(currentIndex + 1) % shapes.length]; + + setLocalLayouts(prev => ({ + ...prev, + [table.name]: { + ...(prev[table.name] || {}), + table_shape: nextShape + } + })); + + updateTableLayout(table.name, { table_shape: nextShape }) + .catch(console.error); + }; + + + const TableShape = ({ table }: { table: typeof tablesWithPosition[0] }) => { + const dimensions = getTableDimensions(table.table_shape, table.no_of_seats); + + const baseClasses = cn( + 'absolute border-2 flex items-center justify-center text-sm font-semibold cursor-pointer transition-all select-none', + getTableStatusColor(table.occupied), + isEditMode && 'hover:ring-2 hover:ring-blue-400 cursor-move', + draggedTable === table.name && 'shadow-xl scale-105 z-20', + selectedTable === table.name && 'ring-2 ring-blue-600 z-10' + ); + + const style = { + left: table.x, + top: table.y, + width: dimensions.width, + height: dimensions.height, + }; + + const shapeLower = table.table_shape?.toLowerCase(); + const shapeClasses = { + circle: 'rounded-full', + square: 'rounded-lg', + rectangle: 'rounded-md' + }; + + const roundedClass = shapeClasses[shapeLower as keyof typeof shapeClasses] || shapeClasses.rectangle; + + return ( +
handleMouseDown(e, table)} + > +
+
{table.name}
+
+ + {table.no_of_seats || '-'} +
+
+ {isEditMode && ( + <> +
+ +
+
handleShapeChange(e, table)} + className="absolute -bottom-1 -right-1 bg-white border border-gray-200 text-gray-600 hover:text-blue-600 rounded-full p-0.5 shadow-sm cursor-pointer z-30 pointer-events-auto" + title="Change Shape" + > + +
+ + )} +
+ ); + }; + + // Helper to format invoice time (consistent with Table.tsx) removed - imported from utils + const handleCapacityChange = (capacityStr: string) => { + if (!selectedTable) return; + const capacity = parseInt(capacityStr); + if (isNaN(capacity) || capacity < 1 || capacity > 20) return; + + const currentTable = tablesWithPosition.find(t => t.name === selectedTable); + if (!currentTable) return; + + setLocalLayouts(prev => ({ + ...prev, + [selectedTable]: { + ...(prev[selectedTable] || {}), + no_of_seats: capacity + } + })); + + updateTableLayout(selectedTable, { no_of_seats: capacity }) + .catch(console.error); + } + + // const handleAddTable = async () => { + // const tableName = prompt("Enter table name:"); + // if (!tableName) return; + + // try { + // await createTable({ + // restaurant_room: selectedRoom, + // table_shape: 'Rectangle', + // no_of_seats: 4, + // custom_layout_x: 100 + Math.abs(panOffset.x), // Place near current view + // custom_layout_y: 100 + Math.abs(panOffset.y), + // is_take_away: 0, + // occupied: 0, + // name: tableName + // }); + // showToast.success('Table created'); + // onRefresh?.(); + // } catch (error) { + // console.error(error); + // showToast.error('Failed to create table'); + // } + // }; + + // const handleDeleteTable = async () => { + // if (!selectedTable) return; + // if (!confirm(`Are you sure you want to delete ${selectedTable}?`)) return; + + // try { + // await deleteTable(selectedTable); + // showToast.success('Table deleted'); + // setSelectedTable(null); + // onRefresh?.(); + // } catch (error) { + // console.error(error); + // showToast.error('Failed to delete table'); + // } + // }; + + const handleDropdownShapeChange = (shape: string) => { + if (!selectedTable) return; + const currentTable = tablesWithPosition.find(t => t.name === selectedTable); + if (!currentTable) return; + + setLocalLayouts(prev => ({ + ...prev, + [selectedTable]: { + ...(prev[selectedTable] || {}), + table_shape: shape as any + } + })); + + updateTableLayout(selectedTable, { table_shape: shape as any }) + .catch(console.error); + } + + const selectedTableData = tablesWithPosition.find(t => t.name === selectedTable); + + return ( +
+ {/* Header Controls */} +
+
+
+ +

{selectedRoom} | Layout

+
+ {/* {isEditMode && ( + + )} */} +
+
+ + {/* Canvas Area */} +
+ {/* Zoom Controls */} +
+ + + +
+ {Math.round(zoom * 100)}% +
+
+ + {/* Edit Mode Toggle */} +
+ +
+ + {/* Instructions */} +
+ {isEditMode ? ( +
+
Editing Layout
+
• Drag tables to reposition
+
• Changes autosave
+
+ ) : ( +
+ Use Scroll to zoom • Drag background to pan +
+ )} +
+ +
+ {/* World Container - applies Zoom and Pan to everything inside */} +
+ {tablesWithPosition.map(table => ( + + ))} +
+
+
+ + {/* Table Properties Panel */} + {selectedTable && selectedTableData && ( +
+
+

+ {isEditMode ? 'Edit Table Settings' : 'Table Info'} +

+ +
+ +
+
+ + +
+ +
+ + handleCapacityChange(e.target.value)} + disabled={!isEditMode} + className={cn( + "w-full px-3 py-2 border rounded-md text-sm", + isEditMode + ? "border-gray-300 bg-white" + : "border-gray-200 bg-gray-50 cursor-not-allowed" + )} + placeholder="Enter 1-20" + /> +

Valid range: 1-20 pax

+
+ +
+ + +
+ +
+ +
+ {selectedTableData.occupied ? 'Occupied' : 'Available'} +
+
+ + {/* Position Information */} +
+ +
+
+ X: + {Math.round(selectedTableData.x)}px +
+
+ Y: + {Math.round(selectedTableData.y)}px +
+
+
+ + {/* Size Information */} +
+ +
+
+ W: + {getTableDimensions(selectedTableData.table_shape || 'Rectangle').width}px +
+
+ H: + {getTableDimensions(selectedTableData.table_shape || 'Rectangle').height}px +
+
+
+ + {/* Show current bill info if table is occupied */} + {selectedTableData.latest_invoice_time && ( +
+ +
+
+ Started at: + {formatInvoiceTime(selectedTableData.latest_invoice_time)} +
+ {selectedTableOrder && ( +
+ Total Amount: + + {selectedTableOrder.grand_total.toFixed(2)} + +
+ )} +
+
+ )} + + {/* {isEditMode && ( +
+ +
+ )} */} +
+
+ )} +
+ ); +}; + +export default LayoutView; \ No newline at end of file diff --git a/pos/src/lib/table-api.ts b/pos/src/lib/table-api.ts index 6bb1bea..235cf3f 100644 --- a/pos/src/lib/table-api.ts +++ b/pos/src/lib/table-api.ts @@ -12,8 +12,11 @@ export interface Table { latest_invoice_time: string | null; is_take_away: number; restaurant_room: string; - table_shape:'Circle' | 'Square' | 'Rectangle'; + table_shape: 'Circle' | 'Square' | 'Rectangle'; no_of_seats?: number; + layout_x?: number; + layout_y?: number; + minimum_seating?: number; } export async function getRestaurantMenu(posProfile: string, room?: string | null) { @@ -38,9 +41,35 @@ export async function getRooms(branch: string): Promise { export async function getTables(room: string): Promise { const { call } = await import('./frappe-sdk'); - const res = await call.get('ury.ury_pos.api.getTable', { room }); - return res.message as Table[]; -} + + const [apiRes, dbRes] = await Promise.all([ + call.get('ury.ury_pos.api.getTable', { room }), + db.getDocList(DOCTYPES.URY_TABLE, { + fields: ['name', 'layout_x', 'layout_y', 'minimum_seating', 'table_shape', 'no_of_seats'], + filters: [['restaurant_room', '=', room]], + limit: 1000, + asDict: true + }) + ]); + + const apiTables = (apiRes.message || []) as Table[]; + const dbTables = (dbRes || []) as any[]; + const dbMap = new Map(dbTables.map(t => [t.name, t])); + + return apiTables.map(t => { + const dbT = dbMap.get(t.name); + return { + ...t, + // Prioritize DB values for configuration fields as standard API might not return custom fields + layout_x: dbT?.layout_x, + layout_y: dbT?.layout_y, + minimum_seating: dbT?.minimum_seating, + // Ensure we have the latest shape/seats from DB as well + table_shape: dbT?.table_shape || t.table_shape, + no_of_seats: dbT?.no_of_seats || t.no_of_seats + }; + }); +} export async function getTableCount(room: string, branch?: string): Promise { const filters = [ @@ -55,4 +84,16 @@ export async function getTableCount(room: string, branch?: string): Promise; const countValue = rows[0]?.count ?? 0; return typeof countValue === 'number' ? countValue : Number(countValue) || 0; +} + +export async function updateTableLayout(name: string, data: Partial
) { + return db.updateDoc(DOCTYPES.URY_TABLE, name, data); +} + +export async function createTable(data: any) { + return db.createDoc(DOCTYPES.URY_TABLE, data); +} + +export async function deleteTable(name: string) { + return db.deleteDoc(DOCTYPES.URY_TABLE, name); } \ No newline at end of file diff --git a/pos/src/lib/utils.ts b/pos/src/lib/utils.ts index 96db635..9d99401 100644 --- a/pos/src/lib/utils.ts +++ b/pos/src/lib/utils.ts @@ -9,4 +9,31 @@ export function cn(...inputs: ClassValue[]) { export function formatCurrency(amount: number): string { const symbol = storage.getItem('currencySymbol'); return `${symbol} ${amount}`; -} \ No newline at end of file +} + +export const formatInvoiceTime = (timestamp: string | null) => { + if (!timestamp) return 'No bill activity yet'; + + const parsedDate = new Date(timestamp); + if (!Number.isNaN(parsedDate.getTime())) { + return parsedDate.toLocaleTimeString(undefined, { hour: 'numeric', minute: 'numeric' }); + } + + const timeOnlyMatch = timestamp.match(/^(\d{1,2}):(\d{2}):(\d{2})(?:\.(\d+))?$/); + if (timeOnlyMatch) { + const [, hours, minutes, seconds] = timeOnlyMatch; + const date = new Date(); + date.setHours(Number(hours), Number(minutes), Number(seconds), 0); + const formatted = date.toLocaleTimeString(undefined, { + hour: '2-digit', + minute: '2-digit', + hour12: false, + }); + if (/^\d{1,2}:\d{2}$/.test(formatted)) { + return formatted; + } + return `${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}`; + } + + return timestamp; + }; \ No newline at end of file diff --git a/pos/src/pages/Table.tsx b/pos/src/pages/Table.tsx index 2c470e4..9ab7382 100644 --- a/pos/src/pages/Table.tsx +++ b/pos/src/pages/Table.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useState, type MouseEvent } from 'react'; import { useNavigate } from 'react-router-dom'; -import { AlertTriangle, Eye, Loader2, Printer, Square, Users } from 'lucide-react'; -import { cn } from '../lib/utils'; +import { AlertTriangle, Eye, Layout, Loader2, Printer, Square, Users } from 'lucide-react'; +import { cn, formatInvoiceTime } from '../lib/utils'; import { usePOSStore } from '../store/pos-store'; import { getRooms, getTables, getTableCount, type Room, type Table } from '../lib/table-api'; import { Spinner } from '../components/ui/spinner'; @@ -13,6 +13,8 @@ import { getTableOrder } from '../lib/order-api'; import { printOrder } from '../lib/print'; import { showToast } from '../components/ui/toast'; +import LayoutView from '../components/LayoutView'; + const sortTables = (tables: Table[]) => [...tables].sort((a, b) => a.name.localeCompare(b.name)); const TableView = () => { @@ -202,33 +204,6 @@ const TableView = () => { } }; - const formatInvoiceTime = (timestamp: string | null) => { - if (!timestamp) return 'No bill activity yet'; - - const parsedDate = new Date(timestamp); - if (!Number.isNaN(parsedDate.getTime())) { - return parsedDate.toLocaleTimeString(undefined, { hour: 'numeric', minute: 'numeric' }); - } - - const timeOnlyMatch = timestamp.match(/^(\d{1,2}):(\d{2}):(\d{2})(?:\.(\d+))?$/); - if (timeOnlyMatch) { - const [, hours, minutes, seconds] = timeOnlyMatch; - const date = new Date(); - date.setHours(Number(hours), Number(minutes), Number(seconds), 0); - const formatted = date.toLocaleTimeString(undefined, { - hour: '2-digit', - minute: '2-digit', - hour12: false, - }); - if (/^\d{1,2}:\d{2}$/.test(formatted)) { - return formatted; - } - return `${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}`; - } - - return timestamp; - }; - const tablesToDisplay = useMemo(() => sortTables(tables), [tables]); const hasRooms = rooms.length > 0; @@ -251,57 +226,78 @@ const TableView = () => { } }; + const [isLayoutView, setIsLayoutView] = useState(false); + + const handleLayoutView = () => { + if (selectedRoom) { + loadTables(selectedRoom, { useCache: false }); + } + setIsLayoutView(true); + }; + + if (isLayoutView && selectedRoom) { + return ( + setIsLayoutView(false)} + /> + ); + } + return (
-
- {loadingRooms && ( -
- -
- )} - - {!loadingRooms && !hasRooms && ( -
- - No rooms found for this branch -
- )} - - {rooms.map(room => ( +
+
+ {loadingRooms && ( +
+ +
+ )} + + {!loadingRooms && !hasRooms && ( +
+ + No rooms found for this branch +
+ )} + + {rooms.map(room => ( + + ))} +
+ +
- ))} +
- - {/*
- -
*/}
@@ -343,42 +339,42 @@ const TableView = () => { )} >
-
+
- - {table.name} + + {table.name}
- {isOccupied ? 'Occupied' : 'Available'} + {isOccupied ? 'Occupied' : 'Available'} -
+
-
+
- Room - {table.restaurant_room} + Room + {table.restaurant_room}
{isOccupied && ( -
+
Started at {formatInvoiceTime(table.latest_invoice_time)} -
+
)} {typeof table.no_of_seats === 'number' && ( -
+
Seats - - {table.no_of_seats} + + {table.no_of_seats} -
+
)} {table.is_take_away === 1 && ( - + Take away - + )} -
+
{isOccupied ? ( From fb2362b32ce3365c27371c33c050b5e78f388627 Mon Sep 17 00:00:00 2001 From: vyshnav mv Date: Wed, 31 Dec 2025 02:16:24 -0800 Subject: [PATCH 02/14] Add: new field to the ury table doc --- ury/ury/doctype/ury_table/ury_table.json | 38 +++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/ury/ury/doctype/ury_table/ury_table.json b/ury/ury/doctype/ury_table/ury_table.json index 0494e01..74f1d60 100644 --- a/ury/ury/doctype/ury_table/ury_table.json +++ b/ury/ury/doctype/ury_table/ury_table.json @@ -16,6 +16,12 @@ "restaurant", "restaurant_room", "branch", + "layout_position_section", + "layout_x", + "layout_width", + "column_break_olsi", + "layout_y", + "layout_height", "section_break_mcm3o", "is_take_away", "active_info_tab", @@ -104,11 +110,40 @@ "fieldtype": "Select", "label": "Table Shape", "options": "\nRectangle\nSquare\nCircle" + }, + { + "fieldname": "layout_position_section", + "fieldtype": "Section Break", + "label": "Layout Position" + }, + { + "fieldname": "layout_x", + "fieldtype": "Float", + "label": "Layout X" + }, + { + "fieldname": "layout_width", + "fieldtype": "Float", + "label": "Layout Width" + }, + { + "fieldname": "column_break_olsi", + "fieldtype": "Column Break" + }, + { + "fieldname": "layout_y", + "fieldtype": "Float", + "label": "Layout Y" + }, + { + "fieldname": "layout_height", + "fieldtype": "Float", + "label": "Layout Height" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-17 11:29:34.334457", + "modified": "2025-12-31 15:45:13.235634", "modified_by": "Administrator", "module": "URY", "name": "URY Table", @@ -157,6 +192,7 @@ } ], "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, "sort_field": "modified", "sort_order": "DESC", "states": [], From b317588176cc6e78b5a81fcd4ea055c433069b6d Mon Sep 17 00:00:00 2001 From: vyshnav mv Date: Tue, 6 Jan 2026 22:56:07 -0800 Subject: [PATCH 03/14] Fix: removed the add button and delete button --- pos/src/components/LayoutView.tsx | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/pos/src/components/LayoutView.tsx b/pos/src/components/LayoutView.tsx index 1210ba2..6c3200d 100644 --- a/pos/src/components/LayoutView.tsx +++ b/pos/src/components/LayoutView.tsx @@ -1,10 +1,10 @@ import React, { useState, useRef, useMemo, useEffect, useCallback } from 'react'; -import { CreditCard as Edit3, Save, Grid3x3 as Grid3X3, ZoomIn, ZoomOut, RotateCcw, X, Users, Move} from 'lucide-react'; +import { CreditCard as Edit3, Save, Square, Circle, RectangleHorizontal, Users, Move, X, Grid3x3 as Grid3X3, ZoomIn, ZoomOut, RotateCcw } from 'lucide-react'; import { cn, formatInvoiceTime } from '../lib/utils'; import { Table, updateTableLayout } from '../lib/table-api'; import { getTableOrder, POSInvoice } from '../lib/order-api'; import { Button } from './ui'; -// import { showToast } from './ui/toast'; +import { showToast } from './ui/toast'; @@ -383,6 +383,7 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef // } // }; + const handleDropdownShapeChange = (shape: string) => { if (!selectedTable) return; const currentTable = tablesWithPosition.find(t => t.name === selectedTable); @@ -418,12 +419,9 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef

{selectedRoom} | Layout

- {/* {isEditMode && ( - - )} */} +
+ {/* Add Table button removed */} +
@@ -464,7 +462,7 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef className={cn( 'flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium shadow-lg border transition-all', isEditMode - ? 'bg-blue-600 hover:bg-blue-700 text-white border-blue-700' + ? 'bg-green-600 hover:bg-green-700 text-white border-green-700' : 'bg-white hover:bg-gray-50 text-gray-700 border-gray-200' )} > @@ -647,18 +645,6 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef )} - {/* {isEditMode && ( -
- -
- )} */} )} From 1ff53e764ff6e2d273db993f8b4cf715a01ee4e8 Mon Sep 17 00:00:00 2001 From: vyshnav mv Date: Tue, 6 Jan 2026 23:07:47 -0800 Subject: [PATCH 04/14] Fix: removed icons that are not used --- pos/src/components/LayoutView.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pos/src/components/LayoutView.tsx b/pos/src/components/LayoutView.tsx index 6c3200d..04b986e 100644 --- a/pos/src/components/LayoutView.tsx +++ b/pos/src/components/LayoutView.tsx @@ -1,10 +1,9 @@ import React, { useState, useRef, useMemo, useEffect, useCallback } from 'react'; -import { CreditCard as Edit3, Save, Square, Circle, RectangleHorizontal, Users, Move, X, Grid3x3 as Grid3X3, ZoomIn, ZoomOut, RotateCcw } from 'lucide-react'; +import { CreditCard as Edit3, Save, Users, Move, X, Grid3x3 as Grid3X3, ZoomIn, ZoomOut, RotateCcw } from 'lucide-react'; import { cn, formatInvoiceTime } from '../lib/utils'; import { Table, updateTableLayout } from '../lib/table-api'; import { getTableOrder, POSInvoice } from '../lib/order-api'; import { Button } from './ui'; -import { showToast } from './ui/toast'; From 631229dcd7aa7cc157f0725f2f20a8cd097f8db5 Mon Sep 17 00:00:00 2001 From: vyshnav mv Date: Tue, 6 Jan 2026 23:14:03 -0800 Subject: [PATCH 05/14] Fix: layout sizing tables scale individually --- pos/src/components/LayoutView.tsx | 130 ++++++++---------------------- 1 file changed, 33 insertions(+), 97 deletions(-) diff --git a/pos/src/components/LayoutView.tsx b/pos/src/components/LayoutView.tsx index 04b986e..75c7d7c 100644 --- a/pos/src/components/LayoutView.tsx +++ b/pos/src/components/LayoutView.tsx @@ -69,61 +69,23 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef } }; - // Helper to zoom around a specific point (screen coordinates) - const zoomToPoint = useCallback((newZoom: number, pivotX: number, pivotY: number) => { - // 1. Calculate the point in World coordinates before zoom - // worldX = (screenX - panX) / oldZoom - const worldPointX = (pivotX - panOffset.x) / zoom; - const worldPointY = (pivotY - panOffset.y) / zoom; - - // 2. Update Zoom - const clampedZoom = Math.max(0.3, Math.min(3, newZoom)); - setZoom(clampedZoom); - - // 3. Calculate new Pan to keep the World point at the same Screen position - // screenX = newPanX + worldX * newZoom - // => newPanX = screenX - worldX * newZoom - setPanOffset({ - x: pivotX - worldPointX * clampedZoom, - y: pivotY - worldPointY * clampedZoom - }); - }, [zoom, panOffset]); - - // Zoom functionality (buttons zoom to center) - const handleZoomIn = () => { - if (!canvasRef.current) return; - const rect = canvasRef.current.getBoundingClientRect(); - const centerX = rect.width / 2; - const centerY = rect.height / 2; - zoomToPoint(zoom + 0.2, centerX, centerY); - }; - const handleZoomOut = () => { - if (!canvasRef.current) return; - const rect = canvasRef.current.getBoundingClientRect(); - const centerX = rect.width / 2; - const centerY = rect.height / 2; - zoomToPoint(zoom - 0.2, centerX, centerY); - }; + // Zoom functionality (simple scale) + const handleZoomIn = () => setZoom(prev => Math.min(prev + 0.1, 3)); + const handleZoomOut = () => setZoom(prev => Math.max(prev - 0.1, 0.3)); const handleResetZoom = () => { setZoom(1); setPanOffset({ x: 0, y: 0 }); }; - // Mouse wheel zoom (zooms to cursor) + // Mouse wheel zoom (simple scale) const handleWheel = useCallback((e: WheelEvent) => { e.preventDefault(); e.stopPropagation(); - - if (!canvasRef.current) return; - const rect = canvasRef.current.getBoundingClientRect(); - const mouseX = e.clientX - rect.left; - const mouseY = e.clientY - rect.top; - const delta = e.deltaY > 0 ? -0.1 : 0.1; - zoomToPoint(zoom + delta, mouseX, mouseY); - }, [zoom, zoomToPoint]); + setZoom(prev => Math.max(0.3, Math.min(3, prev + delta))); + }, []); // Use ref to attach non-passive listener for proper preventDefault useEffect(() => { @@ -160,12 +122,9 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef const canvasRect = canvasRef.current.getBoundingClientRect(); - // We subtract panOffset and divide by zoom to get back to "world" coordinates - const mouseX = (e.clientX - canvasRect.left - panOffset.x) / zoom; - const mouseY = (e.clientY - canvasRect.top - panOffset.y) / zoom; - - const newX = mouseX - dragOffset.x; - const newY = mouseY - dragOffset.y; + // Classic drag math + const newX = (e.clientX - canvasRect.left) / zoom - dragOffset.x - panOffset.x / zoom; + const newY = (e.clientY - canvasRect.top) / zoom - dragOffset.y - panOffset.y / zoom; // Update local state for immediate feedback setLocalLayouts(prev => ({ @@ -234,13 +193,28 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef const canvasRect = canvasRef.current?.getBoundingClientRect(); if (!canvasRect) return; - const mouseXWorld = (e.clientX - canvasRect.left - panOffset.x) / zoom; - const mouseYWorld = (e.clientY - canvasRect.top - panOffset.y) / zoom; + // Calculate offset for drag start + // We need to match the drag math: + // newX = (mouseX - rect.left)/zoom - dragOffset - pan/zoom + // So dragOffset = (mouseX - rect)/zoom - startX - pan/zoom + + // Actually, just use standard offset from top-left of element? + // Wait, the newX formula sets the new TopLeft. + // So dragOffset should be the difference between Mouse and TableTopLeft (in scaled/world units?) + + // User formula: newX = (mouseX - canvasRect.left) / zoom - dragOffset.x - panOffset.x / zoom + + // So when we start drag: + // table.x = (mouseX - rect.left)/zoom - dragOffset.x - panOffset.x/zoom + // dragOffset.x = (mouseX - rect.left)/zoom - table.x - panOffset.x/zoom + + const mouseX = e.clientX; + const mouseY = e.clientY; setDraggedTable(table.name); setDragOffset({ - x: mouseXWorld - table.x, - y: mouseYWorld - table.y + x: (mouseX - canvasRect.left) / zoom - table.x - panOffset.x / zoom, + y: (mouseY - canvasRect.top) / zoom - table.y - panOffset.y / zoom }); }; @@ -281,6 +255,8 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef top: table.y, width: dimensions.width, height: dimensions.height, + transform: `scale(${zoom})`, + transformOrigin: 'top left', }; const shapeLower = table.table_shape?.toLowerCase(); @@ -344,45 +320,6 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef .catch(console.error); } - // const handleAddTable = async () => { - // const tableName = prompt("Enter table name:"); - // if (!tableName) return; - - // try { - // await createTable({ - // restaurant_room: selectedRoom, - // table_shape: 'Rectangle', - // no_of_seats: 4, - // custom_layout_x: 100 + Math.abs(panOffset.x), // Place near current view - // custom_layout_y: 100 + Math.abs(panOffset.y), - // is_take_away: 0, - // occupied: 0, - // name: tableName - // }); - // showToast.success('Table created'); - // onRefresh?.(); - // } catch (error) { - // console.error(error); - // showToast.error('Failed to create table'); - // } - // }; - - // const handleDeleteTable = async () => { - // if (!selectedTable) return; - // if (!confirm(`Are you sure you want to delete ${selectedTable}?`)) return; - - // try { - // await deleteTable(selectedTable); - // showToast.success('Table deleted'); - // setSelectedTable(null); - // onRefresh?.(); - // } catch (error) { - // console.error(error); - // showToast.error('Failed to delete table'); - // } - // }; - - const handleDropdownShapeChange = (shape: string) => { if (!selectedTable) return; const currentTable = tablesWithPosition.find(t => t.name === selectedTable); @@ -497,18 +434,17 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef cursor: isPanning ? 'grabbing' : isEditMode ? 'default' : 'grab' }} > - {/* World Container - applies Zoom and Pan to everything inside */} + {/* World Container - applies Pan Only */}
Date: Tue, 6 Jan 2026 23:26:28 -0800 Subject: [PATCH 06/14] Fix : editing capacity, user can able to type/backspace freely --- pos/src/components/LayoutView.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/pos/src/components/LayoutView.tsx b/pos/src/components/LayoutView.tsx index 75c7d7c..f44f0e4 100644 --- a/pos/src/components/LayoutView.tsx +++ b/pos/src/components/LayoutView.tsx @@ -28,6 +28,7 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef const [panOffset, setPanOffset] = useState({ x: 0, y: 0 }); const [isPanning, setIsPanning] = useState(false); const [panStart, setPanStart] = useState({ x: 0, y: 0 }); + const [capacityInput, setCapacityInput] = useState(''); // Save to local storage effect removed @@ -51,6 +52,14 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef }); }, [tables, localLayouts]); + // Sync capacity input when selected table changes + useEffect(() => { + if (selectedTable) { + const table = tablesWithPosition.find(t => t.name === selectedTable); + setCapacityInput(table?.no_of_seats?.toString() ?? ''); + } + }, [selectedTable, tablesWithPosition]); + // Calculate table dimensions based on capacity and shape const getTableDimensions = (shape: string, capacity: number = 4) => { // Dynamic sizing: minimum 60px, scales up by 10px per person, max 250px @@ -302,6 +311,10 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef // Helper to format invoice time (consistent with Table.tsx) removed - imported from utils const handleCapacityChange = (capacityStr: string) => { if (!selectedTable) return; + + // Always update the input field value to allow free typing + setCapacityInput(capacityStr); + const capacity = parseInt(capacityStr); if (isNaN(capacity) || capacity < 1 || capacity > 20) return; @@ -487,9 +500,9 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef handleCapacityChange(e.target.value)} disabled={!isEditMode} className={cn( From ac999c24c0df3b961cff8163e554ee783efb2d99 Mon Sep 17 00:00:00 2001 From: vyshnav mv Date: Tue, 6 Jan 2026 23:41:22 -0800 Subject: [PATCH 07/14] Fix: -refresh table data after editing finishes and save --- pos/src/components/LayoutView.tsx | 7 ++++++- pos/src/pages/Table.tsx | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pos/src/components/LayoutView.tsx b/pos/src/components/LayoutView.tsx index f44f0e4..8cc7f6d 100644 --- a/pos/src/components/LayoutView.tsx +++ b/pos/src/components/LayoutView.tsx @@ -407,7 +407,12 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef {/* Edit Mode Toggle */}
From 4122ae3c2076fbb1a4b8a6c08d12010bf59a60ba Mon Sep 17 00:00:00 2001 From: vyshnav mv Date: Wed, 7 Jan 2026 03:54:46 -0800 Subject: [PATCH 09/14] Fix: custom api used for simply understand --- pos/src/components/LayoutView.tsx | 2 -- pos/src/lib/table-api.ts | 47 +++---------------------------- pos/src/pages/Table.tsx | 46 +----------------------------- 3 files changed, 5 insertions(+), 90 deletions(-) diff --git a/pos/src/components/LayoutView.tsx b/pos/src/components/LayoutView.tsx index bee7a38..86d7cc1 100644 --- a/pos/src/components/LayoutView.tsx +++ b/pos/src/components/LayoutView.tsx @@ -78,8 +78,6 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef } }; - - // Zoom functionality (simple scale) const handleZoomIn = () => setZoom(prev => Math.min(prev + 0.1, 3)); const handleZoomOut = () => setZoom(prev => Math.max(prev - 0.1, 0.3)); diff --git a/pos/src/lib/table-api.ts b/pos/src/lib/table-api.ts index 235cf3f..5a17fc9 100644 --- a/pos/src/lib/table-api.ts +++ b/pos/src/lib/table-api.ts @@ -19,6 +19,7 @@ export interface Table { minimum_seating?: number; } + export async function getRestaurantMenu(posProfile: string, room?: string | null) { const { call } = await import('./frappe-sdk'); const params: Record = { pos_profile: posProfile }; @@ -41,50 +42,10 @@ export async function getRooms(branch: string): Promise { export async function getTables(room: string): Promise { const { call } = await import('./frappe-sdk'); + const res = await call.get('ury.ury_pos.api.getTable', { room }); + return res.message as Table[]; +} - const [apiRes, dbRes] = await Promise.all([ - call.get('ury.ury_pos.api.getTable', { room }), - db.getDocList(DOCTYPES.URY_TABLE, { - fields: ['name', 'layout_x', 'layout_y', 'minimum_seating', 'table_shape', 'no_of_seats'], - filters: [['restaurant_room', '=', room]], - limit: 1000, - asDict: true - }) - ]); - - const apiTables = (apiRes.message || []) as Table[]; - const dbTables = (dbRes || []) as any[]; - const dbMap = new Map(dbTables.map(t => [t.name, t])); - - return apiTables.map(t => { - const dbT = dbMap.get(t.name); - return { - ...t, - // Prioritize DB values for configuration fields as standard API might not return custom fields - layout_x: dbT?.layout_x, - layout_y: dbT?.layout_y, - minimum_seating: dbT?.minimum_seating, - // Ensure we have the latest shape/seats from DB as well - table_shape: dbT?.table_shape || t.table_shape, - no_of_seats: dbT?.no_of_seats || t.no_of_seats - }; - }); -} - -export async function getTableCount(room: string, branch?: string): Promise { - const filters = [ - ['restaurant_room', '=', room], - ...(branch ? [['branch', '=', branch]] : []), - ]; - const rows = await db.getDocList(DOCTYPES.URY_TABLE, { - fields: ['count(name) as count'], - filters: filters as any, - limit: 1, - asDict: true, - }) as Array<{ count?: number | string }>; - const countValue = rows[0]?.count ?? 0; - return typeof countValue === 'number' ? countValue : Number(countValue) || 0; -} export async function updateTableLayout(name: string, data: Partial
) { return db.updateDoc(DOCTYPES.URY_TABLE, name, data); diff --git a/pos/src/pages/Table.tsx b/pos/src/pages/Table.tsx index af6f397..53a36ad 100644 --- a/pos/src/pages/Table.tsx +++ b/pos/src/pages/Table.tsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { AlertTriangle, Eye, Layout, Loader2, Printer, Square, Users } from 'lucide-react'; import { cn, formatInvoiceTime } from '../lib/utils'; import { usePOSStore } from '../store/pos-store'; -import { getRooms, getTables, getTableCount, type Room, type Table } from '../lib/table-api'; +import { getRooms, getTables, type Room, type Table } from '../lib/table-api'; import { Spinner } from '../components/ui/spinner'; import { Button } from '../components/ui/button'; import { Badge } from '../components/ui/badge'; @@ -29,7 +29,6 @@ const TableView = () => { const [loadingRooms, setLoadingRooms] = useState(false); const [loadingTables, setLoadingTables] = useState(false); const [roomCounts, setRoomCounts] = useState>({}); - const [loadingRoomCounts, setLoadingRoomCounts] = useState(false); const [error, setError] = useState(null); const [printingTable, setPrintingTable] = useState(null); @@ -89,27 +88,6 @@ const TableView = () => { } if (!shouldFetch) return; - - async function fetchRoomCounts() { - setLoadingRoomCounts(true); - try { - const counts = await Promise.all( - rooms.map(room => getTableCount(room.name, room.branch)) - ); - const nextCounts = rooms.reduce((acc, room, index) => { - acc[room.name] = counts[index]; - return acc; - }, {} as Record); - setRoomCounts(nextCounts); - persistRoomCounts(nextCounts); - } catch (error) { - console.error('Failed to load room counts', error); - } finally { - setLoadingRoomCounts(false); - } - } - - fetchRoomCounts(); }, [branch, rooms, persistRoomCounts]); const loadTables = useCallback( @@ -141,23 +119,6 @@ const TableView = () => { [tablesCache] ); - const refreshRoomCount = useCallback(async (roomName: string) => { - const roomMeta = rooms.find(room => room.name === roomName); - const branchName = roomMeta?.branch ?? branch; - if (!roomName || !branchName) return; - - try { - const count = await getTableCount(roomName, branchName); - setRoomCounts(prev => { - const next = { ...prev, [roomName]: count }; - persistRoomCounts(next); - return next; - }); - } catch (error) { - console.error(`Failed to refresh count for room ${roomName}`, error); - } - }, [rooms, branch, persistRoomCounts]); - useEffect(() => { if (!selectedRoom) return; loadTables(selectedRoom); @@ -196,7 +157,6 @@ const TableView = () => { await printOrder({ orderId: invoiceId, posProfile }); showToast.success('Printed successfully'); await loadTables(table.restaurant_room, { useCache: false }); - await refreshRoomCount(table.restaurant_room); } catch (error) { showToast.error(error instanceof Error ? error.message : 'Failed to print order'); } finally { @@ -279,10 +239,6 @@ const TableView = () => { {roomCounts[room.name]} - ) : loadingRoomCounts ? ( - - -- - ) : null} ))} From db1d862febd85c1023ff137970956f172fbc12ee Mon Sep 17 00:00:00 2001 From: vyshnav mv Date: Wed, 7 Jan 2026 04:45:54 -0800 Subject: [PATCH 10/14] Fix: ui fix in for the edit layout button and edit panel --- pos/src/components/LayoutView.tsx | 44 +++++++++++++++---------------- ury/ury_pos/api.py | 3 +-- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/pos/src/components/LayoutView.tsx b/pos/src/components/LayoutView.tsx index 86d7cc1..55c2faa 100644 --- a/pos/src/components/LayoutView.tsx +++ b/pos/src/components/LayoutView.tsx @@ -339,7 +339,26 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef

{selectedRoom} | Layout

- {/* Add Table button removed */} + {/* Edit Mode Toggle */} +
+ +
@@ -374,27 +393,6 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef - {/* Edit Mode Toggle */} -
- -
- {/* Instructions */}
{isEditMode ? ( @@ -446,7 +444,7 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef {/* Table Properties Panel */} {selectedTable && selectedTableData && ( -
+

{isEditMode ? 'Edit Table Settings' : 'Table Info'} diff --git a/ury/ury_pos/api.py b/ury/ury_pos/api.py index 0cbf4b7..6c101c9 100644 --- a/ury/ury_pos/api.py +++ b/ury/ury_pos/api.py @@ -9,12 +9,11 @@ def getTable(room): branch_name = getBranch() tables = frappe.get_all( "URY Table", - fields=["name", "occupied", "latest_invoice_time", "is_take_away", "restaurant_room","table_shape","no_of_seats"], + fields=["name", "occupied", "latest_invoice_time", "is_take_away", "restaurant_room","table_shape","no_of_seats","layout_x","layout_y"], filters={"branch": branch_name,"restaurant_room":room,} ) return tables - @frappe.whitelist() def getRestaurantMenu(pos_profile, room=None, order_type=None): menu_items = [] From 8dee614e0ebfd30f8dd6870d243ce20762866155 Mon Sep 17 00:00:00 2001 From: vyshnav mv Date: Wed, 7 Jan 2026 04:59:35 -0800 Subject: [PATCH 11/14] Fix: removed shadow for edit button and removed not used export api in table-api --- pos/src/components/LayoutView.tsx | 4 ++-- pos/src/lib/table-api.ts | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/pos/src/components/LayoutView.tsx b/pos/src/components/LayoutView.tsx index 55c2faa..a4e6b79 100644 --- a/pos/src/components/LayoutView.tsx +++ b/pos/src/components/LayoutView.tsx @@ -349,7 +349,7 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef setIsEditMode(!isEditMode); }} className={cn( - 'flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium shadow-lg border transition-all', + 'flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium border transition-all', isEditMode ? 'bg-blue-600 hover:bg-blue-700 text-white border-green-700' : 'bg-white hover:bg-gray-50 text-gray-700 border-gray-200' @@ -364,7 +364,7 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef

{/* Canvas Area */} -
+
{/* Zoom Controls */}
) { diff --git a/ury/ury_pos/api.py b/ury/ury_pos/api.py index 6c101c9..dd2cb52 100644 --- a/ury/ury_pos/api.py +++ b/ury/ury_pos/api.py @@ -3,16 +3,16 @@ from datetime import date, datetime, timedelta - -@frappe.whitelist() -def getTable(room): - branch_name = getBranch() - tables = frappe.get_all( - "URY Table", - fields=["name", "occupied", "latest_invoice_time", "is_take_away", "restaurant_room","table_shape","no_of_seats","layout_x","layout_y"], - filters={"branch": branch_name,"restaurant_room":room,} - ) - return tables +#GetTable decripted temporarily +# @frappe.whitelist() +# def getTable(room): +# branch_name = getBranch() +# tables = frappe.get_all( +# "URY Table", +# fields=["name", "occupied", "latest_invoice_time", "is_take_away", "restaurant_room","table_shape","no_of_seats","layout_x","layout_y"], +# filters={"branch": branch_name,"restaurant_room":room,} +# ) +# return tables @frappe.whitelist() def getRestaurantMenu(pos_profile, room=None, order_type=None): From 4086eb7e97dbcde31902dcfb75e11e39c037c003 Mon Sep 17 00:00:00 2001 From: vyshnav mv Date: Wed, 7 Jan 2026 21:28:22 -0800 Subject: [PATCH 13/14] Fix: recall the table count in the room button on header --- pos/src/lib/table-api.ts | 14 ++++++++++++++ pos/src/pages/Table.tsx | 24 +++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/pos/src/lib/table-api.ts b/pos/src/lib/table-api.ts index 0deedfc..b25d16a 100644 --- a/pos/src/lib/table-api.ts +++ b/pos/src/lib/table-api.ts @@ -40,6 +40,20 @@ export async function getRooms(branch: string): Promise { return rooms as Room[]; } +export async function getTableCount(room: string, branch?: string): Promise { + const filters = [ + ['restaurant_room', '=', room], + ...(branch ? [['branch', '=', branch]] : []), + ]; + const rows = await db.getDocList(DOCTYPES.URY_TABLE, { + fields: ['count(name) as count'], + filters: filters as any, + limit: 1, + asDict: true, + }) as Array<{ count?: number | string }>; + const countValue = rows[0]?.count ?? 0; + return typeof countValue === 'number' ? countValue : Number(countValue) || 0; +} export async function getTables(room: string): Promise { const tables = await db.getDocList(DOCTYPES.URY_TABLE, { fields: [ diff --git a/pos/src/pages/Table.tsx b/pos/src/pages/Table.tsx index 53a36ad..33eca0f 100644 --- a/pos/src/pages/Table.tsx +++ b/pos/src/pages/Table.tsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { AlertTriangle, Eye, Layout, Loader2, Printer, Square, Users } from 'lucide-react'; import { cn, formatInvoiceTime } from '../lib/utils'; import { usePOSStore } from '../store/pos-store'; -import { getRooms, getTables, type Room, type Table } from '../lib/table-api'; +import { getRooms, getTables, getTableCount ,type Room, type Table } from '../lib/table-api'; import { Spinner } from '../components/ui/spinner'; import { Button } from '../components/ui/button'; import { Badge } from '../components/ui/badge'; @@ -29,6 +29,7 @@ const TableView = () => { const [loadingRooms, setLoadingRooms] = useState(false); const [loadingTables, setLoadingTables] = useState(false); const [roomCounts, setRoomCounts] = useState>({}); + const [loadingRoomCounts, setLoadingRoomCounts] = useState(false); const [error, setError] = useState(null); const [printingTable, setPrintingTable] = useState(null); @@ -88,6 +89,27 @@ const TableView = () => { } if (!shouldFetch) return; + + async function fetchRoomCounts() { + setLoadingRoomCounts(true); + try { + const counts = await Promise.all( + rooms.map(room => getTableCount(room.name, room.branch)) + ); + const nextCounts = rooms.reduce((acc, room, index) => { + acc[room.name] = counts[index]; + return acc; + }, {} as Record); + setRoomCounts(nextCounts); + persistRoomCounts(nextCounts); + } catch (error) { + console.error('Failed to load room counts', error); + } finally { + setLoadingRoomCounts(false); + } + } + + fetchRoomCounts(); }, [branch, rooms, persistRoomCounts]); const loadTables = useCallback( From d4897620df6d0a1e4703605bcd99e9244282a58b Mon Sep 17 00:00:00 2001 From: vyshnav mv Date: Mon, 12 Jan 2026 07:25:37 -0800 Subject: [PATCH 14/14] Fix: canvas active planing as no limit view --- pos/src/components/LayoutView.tsx | 64 +++++++++++++++---------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/pos/src/components/LayoutView.tsx b/pos/src/components/LayoutView.tsx index a4e6b79..1a4ca94 100644 --- a/pos/src/components/LayoutView.tsx +++ b/pos/src/components/LayoutView.tsx @@ -340,25 +340,25 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef
{/* Edit Mode Toggle */} -
- -
+
+ +
@@ -410,30 +410,26 @@ const LayoutView: React.FC = ({ selectedRoom, tables, onBackToGrid, onRef
- {/* World Container - applies Pan Only */} + {/* Tables Container with Transform */}
{tablesWithPosition.map(table => (