"use client"; import { ArrowTopRightOnSquareIcon, MapPinIcon, PlusIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline"; import { useMemo, useState } from "react"; import { FormField, inputClassName } from "@/app/_components/form-field"; import { uid } from "@/lib/hellth-data"; import { trpc } from "@/lib/trpc"; type DepartmentDraft = { id: string; name: string; sortOrder: number; }; type StoreDraft = { id?: string; name: string; address?: string | null; placeId?: string | null; latitude?: number | null; longitude?: number | null; favorite: boolean; departments: DepartmentDraft[]; }; function defaultDepartments(storeId: string): DepartmentDraft[] { return [ { id: `${storeId}-produce`, name: "Obst & Gemüse", sortOrder: 1 }, { id: `${storeId}-bakery`, name: "Brot & Backwaren", sortOrder: 2 }, { id: `${storeId}-dairy`, name: "Milchprodukte", sortOrder: 3 }, { id: `${storeId}-protein`, name: "Fleisch/Fisch/Protein", sortOrder: 4 }, { id: `${storeId}-drinks`, name: "Getränke", sortOrder: 5 }, { id: `${storeId}-other`, name: "Sonstiges", sortOrder: 99 }, ]; } function mapsSearchUrl(name: string, address?: string | null) { return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent([name, address].filter(Boolean).join(" "))}`; } export default function MaerktePage() { const utils = trpc.useUtils(); const [storeName, setStoreName] = useState(""); const [storeAddress, setStoreAddress] = useState(""); const [storeLat, setStoreLat] = useState(""); const [storeLng, setStoreLng] = useState(""); const [departmentNames, setDepartmentNames] = useState>({}); const [changeNotes, setChangeNotes] = useState>({}); const [status, setStatus] = useState(null); const storesQuery = trpc.health.listStores.useQuery(); const requestsQuery = trpc.health.myStoreChangeRequests.useQuery(); const saveStoreMutation = trpc.health.saveStore.useMutation({ onSuccess: async () => { await utils.health.listStores.invalidate(); setStatus("Markt gespeichert."); }, }); const submitStoreChangeRequest = trpc.health.submitStoreChangeRequest.useMutation({ onSuccess: async () => { await utils.health.myStoreChangeRequests.invalidate(); setStatus("Änderungsantrag eingereicht."); }, }); const setStoreFavoriteMutation = trpc.health.setStoreFavorite.useMutation({ onSuccess: () => utils.health.listStores.invalidate(), }); const deleteStoreMutation = trpc.health.deleteStore.useMutation({ onSuccess: () => utils.health.listStores.invalidate(), }); const stores = useMemo(() => storesQuery.data ?? [], [storesQuery.data]); function saveDraft(draft: StoreDraft) { saveStoreMutation.mutate({ id: draft.id, name: draft.name, address: draft.address || null, placeId: draft.placeId || null, latitude: draft.latitude ?? null, longitude: draft.longitude ?? null, favorite: draft.favorite, departments: draft.departments .map((department, index) => ({ id: department.id, name: department.name, sortOrder: department.sortOrder || index + 1, })) .sort((a, b) => a.sortOrder - b.sortOrder), }); } function addStore() { if (!storeName.trim()) return; const storeId = uid("store"); saveDraft({ id: storeId, name: storeName.trim(), address: storeAddress.trim() || null, latitude: storeLat ? Number(storeLat.replace(",", ".")) : null, longitude: storeLng ? Number(storeLng.replace(",", ".")) : null, favorite: false, departments: defaultDepartments(storeId), }); setStoreName(""); setStoreAddress(""); setStoreLat(""); setStoreLng(""); } function useCurrentLocation() { if (!navigator.geolocation) { setStatus("Standortfreigabe ist in diesem Browser nicht verfügbar."); return; } navigator.geolocation.getCurrentPosition( (position) => { setStoreLat(position.coords.latitude.toFixed(6)); setStoreLng(position.coords.longitude.toFixed(6)); setStatus("Standort übernommen. Bitte Marktname und Adresse ergänzen."); }, () => setStatus("Standort konnte nicht gelesen werden."), { enableHighAccuracy: true, timeout: 10000 }, ); } function toggleFavorite(store: StoreDraft) { if (!store.id) return; setStoreFavoriteMutation.mutate({ storeId: store.id, favorite: !store.favorite }); } function submitChange(store: StoreDraft, requestType: "new_store" | "structure_update" | "location_update" = "structure_update") { submitStoreChangeRequest.mutate({ storeId: store.id ?? null, requestType, title: `${store.name}: Änderung übernehmen`, note: changeNotes[store.id ?? store.name] || null, store: { name: store.name, address: store.address ?? null, placeId: store.placeId ?? null, latitude: store.latitude ?? null, longitude: store.longitude ?? null, }, departments: store.departments, }); } function addDepartment(store: StoreDraft) { const name = departmentNames[store.id ?? store.name]?.trim(); if (!name) return; const maxOrder = Math.max(0, ...store.departments.map((department) => department.sortOrder)); saveDraft({ ...store, departments: [ ...store.departments, { id: uid(`${store.id ?? "store"}-dept`), name, sortOrder: maxOrder + 1 }, ], }); setDepartmentNames((current) => ({ ...current, [store.id ?? store.name]: "" })); } function removeDepartment(store: StoreDraft, departmentId: string) { if (store.departments.length <= 1) return; saveDraft({ ...store, departments: store.departments.filter((department) => department.id !== departmentId) }); } function moveDepartment(store: StoreDraft, departmentId: string, direction: -1 | 1) { const sorted = [...store.departments].sort((a, b) => a.sortOrder - b.sortOrder); const index = sorted.findIndex((department) => department.id === departmentId); const targetIndex = index + direction; if (index < 0 || targetIndex < 0 || targetIndex >= sorted.length) return; const current = sorted[index]!; const target = sorted[targetIndex]!; sorted[index] = { ...current, sortOrder: target.sortOrder }; sorted[targetIndex] = { ...target, sortOrder: current.sortOrder }; saveDraft({ ...store, departments: sorted }); } return (

Einkaufen

Märkte & Laufwege

Speichere nahe Lebensmittelläden, pinne ihren Standort und sortiere die Abteilungen so, wie du wirklich durch den Markt läufst.

setStoreName(event.target.value)} placeholder="z. B. Kaufland" className={inputClassName} /> setStoreAddress(event.target.value)} placeholder="Straße, Ort" className={inputClassName} /> setStoreLat(event.target.value)} placeholder="48.3705" className={inputClassName} /> setStoreLng(event.target.value)} placeholder="10.8978" className={inputClassName} />
{stores.map((store) => { const canEdit = store.ownerUserId !== null; const draft: StoreDraft = { id: store.id, name: store.name, address: store.address, placeId: store.placeId, latitude: store.latitude, longitude: store.longitude, favorite: store.favorite, departments: store.departments.map((department) => ({ id: department.id, name: department.name, sortOrder: department.sortOrder, })), }; const departmentKey = store.id; return (

{store.name}

{store.departments.length} Abteilungen {!canEdit ? " · globale Vorlage" : " · eigener Markt"}

{store.address ?

{store.address}

: null} {store.latitude != null && store.longitude != null ? ( Pin öffnen ) : null}
{canEdit ? ( ) : null}
{[...store.departments].sort((a, b) => a.sortOrder - b.sortOrder).map((department, index) => (
{index + 1} {department.name}
{canEdit ? ( <> ) : null}
{canEdit ? ( ) : }
))}
{canEdit ? (
setDepartmentNames((current) => ({ ...current, [departmentKey]: event.target.value }))} placeholder="Neue Abteilung" className={inputClassName} />
) : null}