Skip to content
This repository was archived by the owner on Nov 18, 2025. It is now read-only.

Commit d63e3df

Browse files
Moving entrance and exit points
1 parent a28ca22 commit d63e3df

File tree

5 files changed

+166
-100
lines changed

5 files changed

+166
-100
lines changed

backend/admin-panel/src/hooks/use-stores.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,25 @@ export function useUpdateStore() {
5454
const queryClient = useQueryClient()
5555

5656
return useMutation({
57-
mutationFn: async (slug: string) => {
57+
mutationFn: async ({
58+
slug,
59+
...body
60+
}: {
61+
slug: string
62+
name?: string
63+
entranceX?: number
64+
entranceY?: number
65+
exitX?: number
66+
exitY?: number
67+
}) => {
5868
const { data, error } = await client.PUT('/api/store/{slug}', {
5969
params: { path: { slug } },
70+
body,
6071
})
6172
if (error) throw error
6273
return data
6374
},
64-
onSuccess: (_, slug) => {
75+
onSuccess: (_, { slug }) => {
6576
queryClient.invalidateQueries(getStoresOptions())
6677
queryClient.invalidateQueries(getStoreOptions(slug))
6778
},

backend/admin-panel/src/lib/api.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,22 @@ const { VITE_BASE_API_URL } = z
1313
export const client = createClient<paths>({
1414
baseUrl: VITE_BASE_API_URL,
1515
})
16+
17+
// Typescript magic ;)
18+
export type ResponseType<
19+
TPath extends keyof paths,
20+
TMethod extends keyof paths[TPath],
21+
TStatus extends keyof paths[TPath][TMethod] extends never
22+
? never
23+
: paths[TPath][TMethod] extends { responses: infer R }
24+
? keyof R
25+
: never,
26+
> = paths[TPath][TMethod] extends { responses: infer R }
27+
? TStatus extends keyof R
28+
? R[TStatus] extends { content: infer C }
29+
? C extends { 'application/json': infer J }
30+
? J
31+
: never
32+
: never
33+
: never
34+
: never

backend/admin-panel/src/routes/stores/$slug/aisles.tsx

Lines changed: 105 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -6,54 +6,26 @@ import {
66
useDeleteAisle,
77
useUpdateAisle,
88
getAisleTypesOptions,
9+
useUpdateStore,
910
} from '@/hooks/use-stores'
1011
import { Button } from '@/components/ui/button'
1112
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
12-
import { Badge } from '@/components/ui/badge'
13-
import {
14-
Table,
15-
TableBody,
16-
TableCell,
17-
TableHead,
18-
TableHeader,
19-
TableRow,
20-
} from '@/components/ui/table'
2113
import {
2214
Select,
2315
SelectContent,
2416
SelectItem,
2517
SelectTrigger,
2618
SelectValue,
2719
} from '@/components/ui/select'
28-
import {
29-
ArrowLeft,
30-
Plus,
31-
Trash2,
32-
Move,
33-
Maximize2,
34-
Pencil,
35-
Save,
36-
} from 'lucide-react'
20+
import { ArrowLeft, Trash2, Move, Maximize2, Pencil, Save } from 'lucide-react'
3721
import { useSuspenseQuery } from '@tanstack/react-query'
3822
import { cn } from '@/lib/utils'
3923
import { useState, useRef, useEffect } from 'react'
24+
import type { ResponseType } from '@/lib/api'
4025

4126
type Tool = 'draw' | 'move' | 'resize' | 'delete'
4227

43-
type AisleType =
44-
| 'OBSTACLE'
45-
| 'FREEZER'
46-
| 'DRINKS'
47-
| 'PANTRY'
48-
| 'SWEETS'
49-
| 'CHEESE'
50-
| 'MEAT'
51-
| 'DAIRY'
52-
| 'FRIDGE'
53-
| 'FRUIT'
54-
| 'VEGETABLES'
55-
| 'BAKERY'
56-
| 'OTHER'
28+
type AisleType = ResponseType<'/api/resources/aisle-types', 'get', 200>[number]
5729

5830
interface Aisle {
5931
id: string
@@ -119,6 +91,7 @@ function AislesPage() {
11991
const createAisle = useCreateAisle()
12092
const deleteAisle = useDeleteAisle()
12193
const updateAisle = useUpdateAisle()
94+
const updateStore = useUpdateStore()
12295

12396
// Drawing state
12497
const [activeTool, setActiveTool] = useState<Tool | null>(null)
@@ -128,6 +101,25 @@ function AislesPage() {
128101
const [modifiedAisles, setModifiedAisles] = useState<
129102
Map<string, ModifiedAisle>
130103
>(new Map())
104+
105+
const [selectedPoint, setSelectedPoint] = useState<
106+
'entrance' | 'exit' | null
107+
>(null)
108+
const [localEntranceCoords, setLocalEntranceCoords] = useState<{
109+
x: number
110+
y: number
111+
}>({
112+
x: store.entranceX,
113+
y: store.entranceY,
114+
})
115+
const [localExitCoords, setLocalExitCoords] = useState<{
116+
x: number
117+
y: number
118+
}>({
119+
x: store.exitX,
120+
y: store.exitY,
121+
})
122+
131123
const [deletedAisles, setDeletedAisles] = useState<Set<string>>(new Set())
132124
const [newAisles, setNewAisles] = useState<ModifiedAisle[]>([])
133125

@@ -153,17 +145,13 @@ function AislesPage() {
153145
}, [aisles])
154146

155147
const hasModifications =
156-
modifiedAisles.size > 0 || deletedAisles.size > 0 || newAisles.length > 0
157-
158-
const handleCreateAisle = () => {
159-
createAisle.mutate({ slug })
160-
}
161-
162-
const handleDeleteAisle = (aisleId: string) => {
163-
if (confirm('Are you sure you want to delete this aisle?')) {
164-
deleteAisle.mutate({ slug, aisleId })
165-
}
166-
}
148+
modifiedAisles.size > 0 ||
149+
deletedAisles.size > 0 ||
150+
newAisles.length > 0 ||
151+
store.entranceX !== localEntranceCoords.x ||
152+
store.entranceY !== localEntranceCoords.y ||
153+
store.exitX !== localExitCoords.x ||
154+
store.exitY !== localExitCoords.y
167155

168156
const getGridCoordinates = (
169157
clientX: number,
@@ -238,6 +226,19 @@ function AislesPage() {
238226
}
239227
} else if (activeTool === 'move') {
240228
const aisle = findAisleAtPosition(coords.x, coords.y)
229+
if (
230+
coords.x === localEntranceCoords.x &&
231+
coords.y === localEntranceCoords.y
232+
) {
233+
setSelectedPoint('entrance')
234+
setDragStart(coords)
235+
return
236+
}
237+
if (coords.x === localExitCoords.x && coords.y === localExitCoords.y) {
238+
setSelectedPoint('exit')
239+
setDragStart(coords)
240+
return
241+
}
241242
if (aisle) {
242243
setSelectedAisleId(aisle.id)
243244
setDragStart(coords)
@@ -337,6 +338,16 @@ function AislesPage() {
337338
)
338339
setDragStart(coords)
339340
}
341+
} else if (activeTool === 'move' && selectedPoint && dragStart) {
342+
const newCoords = getGridCoordinates(e.clientX, e.clientY)
343+
if (newCoords) {
344+
if (selectedPoint === 'entrance') {
345+
setLocalEntranceCoords(newCoords)
346+
}
347+
if (selectedPoint === 'exit') {
348+
setLocalExitCoords(newCoords)
349+
}
350+
}
340351
}
341352
}
342353

@@ -391,6 +402,7 @@ function AislesPage() {
391402
setSelectedAisleId(null)
392403
setDragStart(null)
393404
setResizeHandle(null)
405+
setSelectedPoint(null)
394406
}
395407

396408
const handleSaveChanges = async () => {
@@ -429,6 +441,14 @@ function AislesPage() {
429441
await deleteAisle.mutateAsync({ slug, aisleId })
430442
}
431443

444+
await updateStore.mutateAsync({
445+
slug,
446+
entranceX: localEntranceCoords.x,
447+
entranceY: localEntranceCoords.y,
448+
exitX: localExitCoords.x,
449+
exitY: localExitCoords.y,
450+
})
451+
432452
// Clear modifications
433453
setModifiedAisles(new Map())
434454
setDeletedAisles(new Set())
@@ -445,6 +465,9 @@ function AislesPage() {
445465
setDeletedAisles(new Set())
446466
setNewAisles([])
447467
setActiveTool(null)
468+
setSelectedPoint(null)
469+
setLocalEntranceCoords({ x: store.entranceX, y: store.entranceY })
470+
setLocalExitCoords({ x: store.exitX, y: store.exitY })
448471
}
449472

450473
const getPreviewRectangle = () => {
@@ -470,59 +493,6 @@ function AislesPage() {
470493
</Button>
471494
</div>
472495

473-
<Card className="mb-6">
474-
<CardHeader className="flex flex-row items-center justify-between">
475-
<CardTitle>Aisles - {store?.name}</CardTitle>
476-
<Button onClick={handleCreateAisle} disabled={createAisle.isPending}>
477-
<Plus className="mr-2 h-4 w-4" />
478-
{createAisle.isPending ? 'Creating...' : 'New Aisle'}
479-
</Button>
480-
</CardHeader>
481-
<CardContent>
482-
<Table>
483-
<TableHeader>
484-
<TableRow>
485-
<TableHead>Type</TableHead>
486-
<TableHead>Position (X, Y)</TableHead>
487-
<TableHead>Size (W × H)</TableHead>
488-
<TableHead>Actions</TableHead>
489-
</TableRow>
490-
</TableHeader>
491-
<TableBody>
492-
{aisles?.map((aisle) => (
493-
<TableRow key={aisle.id}>
494-
<TableCell>
495-
<Badge
496-
variant={
497-
aisle.type === 'OBSTACLE' ? 'destructive' : 'secondary'
498-
}
499-
>
500-
{aisle.type}
501-
</Badge>
502-
</TableCell>
503-
<TableCell>
504-
({aisle.gridX}, {aisle.gridY})
505-
</TableCell>
506-
<TableCell>
507-
{aisle.width} × {aisle.height}
508-
</TableCell>
509-
<TableCell>
510-
<Button
511-
variant="ghost"
512-
size="sm"
513-
onClick={() => handleDeleteAisle(aisle.id)}
514-
disabled={deleteAisle.isPending}
515-
>
516-
<Trash2 className="h-4 w-4 text-red-500" />
517-
</Button>
518-
</TableCell>
519-
</TableRow>
520-
))}
521-
</TableBody>
522-
</Table>
523-
</CardContent>
524-
</Card>
525-
526496
{/* Grid Visualization with Drawing Tools */}
527497
<Card>
528498
<CardHeader className="flex flex-row items-center justify-between">
@@ -628,7 +598,7 @@ function AislesPage() {
628598
<div className="relative bg-gray-100 p-4 rounded-lg overflow-auto">
629599
<div
630600
ref={gridRef}
631-
className="grid bg-[#434343] cursor-crosshair"
601+
className="grid bg-[#434343] cursor-crosshair w-fit"
632602
style={{
633603
gridTemplateColumns: 'repeat(64, 10px)',
634604
gridTemplateRows: 'repeat(64, 10px)',
@@ -676,6 +646,44 @@ function AislesPage() {
676646
</div>
677647
))}
678648

649+
{/* Entrance */}
650+
651+
<div
652+
id="store-entrance"
653+
className={cn(
654+
'bg-green-400',
655+
activeTool === 'move' && 'cursor-move',
656+
activeTool === 'resize' && 'cursor-not-allowed',
657+
activeTool === 'delete' && 'cursor-not-allowed',
658+
)}
659+
style={{
660+
gridColumnStart: localEntranceCoords.x + 1,
661+
gridColumnEnd: localEntranceCoords.x + 1,
662+
gridRowStart: localEntranceCoords.y + 1,
663+
gridRowEnd: localEntranceCoords.y + 1,
664+
}}
665+
>
666+
Enter
667+
</div>
668+
{/* Exit */}
669+
<div
670+
id="store-exit"
671+
className={cn(
672+
'bg-red-400',
673+
activeTool === 'move' && 'cursor-move',
674+
activeTool === 'resize' && 'cursor-not-allowed',
675+
activeTool === 'delete' && 'cursor-not-allowed',
676+
)}
677+
style={{
678+
gridColumnStart: localExitCoords.x + 1,
679+
gridColumnEnd: localExitCoords.x + 1,
680+
gridRowStart: localExitCoords.y + 1,
681+
gridRowEnd: localExitCoords.y + 1,
682+
}}
683+
>
684+
Exit
685+
</div>
686+
679687
{/* Draw preview */}
680688
{preview && (
681689
<div

0 commit comments

Comments
 (0)