{ "backgroundColor": "{{appsmith.theme.colors.backgroundColor}}", "borderColor": "{{appsmith.theme.colors.backgroundColor}}", "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", "borderWidth": "1", "bottomRow": 79, "boxShadow": "none", "defaultModel": "{{\n {\n seats: GetSeats.data || []\n }\n}}", "dynamicBindingPathList": [ { "key": "theme" }, { "key": "borderRadius" }, { "key": "backgroundColor" }, { "key": "borderColor" }, { "key": "defaultModel" } ], "dynamicHeight": "FIXED", "dynamicPropertyPathList": [ { "key": "seatSelected" } ], "dynamicTriggerPathList": [ { "key": "seatSelected" } ], "events": [ "seatSelected" ], "isCanvas": false, "isLoading": false, "isSearchWildcard": true, "isVisible": true, "key": "22lyh696pi", "leftColumn": 13, "maxDynamicHeight": 9000, "minDynamicHeight": 4, "mobileBottomRow": 59, "mobileLeftColumn": 8, "mobileRightColumn": 31, "mobileTopRow": 29, "needsErrorInfo": false, "parentColumnSpace": 19.546875, "parentId": "0", "parentRowSpace": 10, "renderMode": "CANVAS", "rightColumn": 64, "seatSelected": "{{ showModal(CalendarModal.name) }}", "srcDoc": { "css": "@charset \"UTF-8\";\nhtml,\nbody {\n margin: 0;\n width: 100%;\n height: 100%;\n overflow: auto;\n font-family: Arial, sans-serif;\n}\n\n/* Bild + Overlay */\n#imgHolder {\n position: relative;\n width: 100%;\n height: 700px; /* feste Höhe, damit sicher nicht 0 */\n overflow: hidden;\n border-radius: 6px;\n background: #f3f4f6;\n}\n\n#floorplanImage {\n position: absolute;\n inset: 0;\n z-index: 1;\n display: block;\n width: 100%;\n height: 100%;\n object-fit: contain;\n}\n\n#seatLayer {\n position: absolute;\n inset: 0;\n z-index: 2;\n width: 100%;\n height: 100%;\n pointer-events: none; /* Punkte bekommen später pointer-events:auto */\n}\n\n/* Sitzpunkte */\n.seat {\n position: absolute;\n z-index: 3;\n display: block;\n width: 18px;\n height: 18px;\n padding: 0;\n transform: translate(-50%, -50%);\n border: 2px solid #ffffff;\n border-radius: 50%;\n background: #22c55e;\n color: #22c55e;\n cursor: pointer;\n pointer-events: auto;\n appearance: none;\n box-sizing: border-box;\n box-shadow: 0 0 5px currentColor, 0 0 10px currentColor;\n transition: transform 0.15s ease, box-shadow 0.15s ease;\n}\n\n.seat:hover {\n z-index: 10;\n transform: translate(-50%, -50%) scale(1.45);\n box-shadow: 0 0 8px currentColor, 0 0 16px currentColor;\n}\n\n/* Farben nach Status */\n.seat.frei {\n background: #22c55e; /* Grün */\n color: #22c55e;\n}\n\n.seat.belegt {\n background: #ef4444; /* Rot */\n color: #ef4444;\n}\n\n.seat.selected {\n outline: 3px solid #2563eb;\n outline-offset: 3px;\n}\n\n/* Legende */\n#legend {\n display: flex;\n flex-wrap: wrap;\n gap: 14px;\n margin-top: 8px;\n color: #374151;\n font-size: 13px;\n}\n\n.legendItem {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n}\n\n.legendColor {\n display: inline-block;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n}\n\n.legendColor.frei {\n background: #22c55e;\n}\n\n.legendColor.belegt {\n background: #ef4444;\n}", "html": "
updateModel({\n selectedSeat: seat,\n calendarView: \"dayGridMonth\",\n selectedDate: null,\n calendarMode: \"seat\"\n});function getModel() {\n const model = appsmith.model || {};\n\n if (typeof model === \"string\") {\n try {\n return JSON.parse(model);\n } catch (error) {\n return {};\n }\n }\n\n return model;\n}\n\n/**\n * 40 statische Plätze erzeugen (Fallback, wenn noch keine DB-Daten)\n */\nfunction createDefaultSeats() {\n const seats = [];\n let number = 1;\n\n const columns = 8; // 8 x 5 = 40\n const rows = 5;\n\n for (let row = 0; row < rows; row++) {\n for (let column = 0; column < columns; column++) {\n seats.push({\n id: `P${String(number).padStart(2, \"0\")}`,\n name: `Platz P${String(number).padStart(2, \"0\")}`,\n x: 10 + column * 11.0, // X in Prozent\n y: 15 + row * 17.0, // Y in Prozent\n status: \"frei\"\n });\n number++;\n }\n }\n\n return seats;\n}\n\n/**\n * Plätze aus dem Model lesen, ansonsten statische 40 zurückgeben\n */\nfunction getSeats() {\n const model = getModel();\n\n let seats = model.seats;\n\n if (typeof seats === \"string\") {\n try {\n seats = JSON.parse(seats);\n } catch (error) {\n seats = [];\n }\n }\n\n if (seats && Array.isArray(seats.data)) {\n seats = seats.data;\n }\n\n if (!Array.isArray(seats) || seats.length === 0) {\n // Fallback: 40 statische Plätze\n return createDefaultSeats();\n }\n\n return seats;\n}\n\n/**\n * Status-Text für Anzeige\n */\nfunction getStatusText(status) {\n const s = String(status || \"\").trim().toLowerCase();\n\n if (s === \"belegt\" || s === \"gebucht\" || s === \"booked\" || s === \"occupied\") {\n return \"Belegt\";\n }\n\n return \"Frei\";\n}\n\nfunction updateSelectedSeatText() {\n const textElement = document.getElementById(\"selectedSeatText\");\n if (!textElement) return;\n\n if (!selectedSeat) {\n textElement.textContent = \"Kein Platz ausgewählt\";\n return;\n }\n\n textElement.textContent =\n `${selectedSeat.name} – Status: ${getStatusText(selectedSeat.status)}`;\n}\n\nfunction selectSeat(seat) {\n const status = String(seat.status || \"frei\")\n .trim()\n .toLowerCase();\n\n if (status !== \"frei\" && status !== \"available\") {\n return;\n }\n\n selectedSeat = seat;\n\n appsmith.updateModel({\n selectedSeat: seat\n });\n\n updateSelectedSeatText();\n renderSeats();\n\n try {\n appsmith.triggerEvent(\"seatSelected\");\n } catch (error) {\n // Event evtl. noch nicht konfiguriert\n }\n}\n\n/**\n * Auswahl löschen\n */\nfunction clearSelection() {\n selectedSeat = null;\n\n appsmith.updateModel({\n selectedSeat: null\n });\n\n updateSelectedSeatText();\n renderSeats();\n}\n\n/**\n * Button (Punkt) für einen Platz erzeugen\n */\nfunction createSeatButton(seat) {\n const button = document.createElement(\"button\");\n\n button.type = \"button\";\n button.classList.add(\"seat\");\n\n const s = String(seat.status || \"frei\").trim().toLowerCase();\n const isBooked =\n s === \"belegt\" ||\n s === \"gebucht\" ||\n s === \"booked\" ||\n s === \"occupied\";\n\n if (isBooked) {\n button.classList.add(\"belegt\");\n } else {\n button.classList.add(\"frei\");\n }\n\n if (selectedSeat && String(selectedSeat.id) === String(seat.id)) {\n button.classList.add(\"selected\");\n }\n\n button.style.left = `${Number(seat.x)}%`;\n button.style.top = `${Number(seat.y)}%`;\n\n button.title = `${seat.name} – ${getStatusText(seat.status)}`;\n\n button.addEventListener(\"click\", () => {\n selectSeat(seat);\n });\n\n return button;\n}\n\n/**\n * Plätze auf dem Bild rendern\n */\nfunction renderSeats() {\n const layer = document.getElementById(\"seatLayer\");\n const image = document.getElementById(\"floorplanImage\");\n\n if (!layer || !image) {\n return;\n }\n\n // Bild per Base64 setzen\n image.src = FLOORPLAN_IMAGE;\n\n // Punkte neu zeichnen\n layer.innerHTML = \"\";\n\n const seats = getSeats();\n\n seats.forEach((seat) => {\n layer.appendChild(createSeatButton(seat));\n });\n\n updateSelectedSeatText();\n}\n\n/**\n * Events registrieren\n */\nfunction registerUiEvents() {\n const clearButton = document.getElementById(\"clearSelectionButton\");\n\n if (clearButton) {\n clearButton.addEventListener(\"click\", clearSelection);\n }\n}\n\n/**\n * Einstiegspunkt für das Custom Widget\n */\nappsmith.onReady(() => {\n registerUiEvents();\n renderSeats();\n\n appsmith.onModelChange(() => {\n renderSeats();\n });\n});\n\nwo füge ich das ein?" }, "theme": "{{appsmith.theme}}", "topRow": 8, "type": "CUSTOM_WIDGET", "uncompiledSrcDoc": { "css": "html,\nbody {\n margin: 0;\n width: 100%;\n height: 100%;\n overflow: auto;\n font-family: Arial, sans-serif;\n}\n\n\n/* Bild + Overlay */\n#imgHolder {\n position: relative;\n width: 100%;\n height: 700px; /* feste Höhe, damit sicher nicht 0 */\n overflow: hidden;\n border-radius: 6px;\n background: #f3f4f6;\n}\n\n#floorplanImage {\n position: absolute;\n inset: 0;\n z-index: 1;\n display: block;\n width: 100%;\n height: 100%;\n object-fit: contain;\n}\n\n#seatLayer {\n position: absolute;\n inset: 0;\n z-index: 2;\n width: 100%;\n height: 100%;\n pointer-events: none; /* Punkte bekommen später pointer-events:auto */\n}\n\n/* Sitzpunkte */\n.seat {\n position: absolute;\n z-index: 3;\n display: block;\n width: 18px;\n height: 18px;\n padding: 0;\n transform: translate(-50%, -50%);\n border: 2px solid #ffffff;\n border-radius: 50%;\n background: #22c55e;\n color: #22c55e;\n cursor: pointer;\n pointer-events: auto;\n appearance: none;\n box-sizing: border-box;\n box-shadow:\n 0 0 5px currentColor,\n 0 0 10px currentColor;\n transition:\n transform 0.15s ease,\n box-shadow 0.15s ease;\n}\n\n.seat:hover {\n z-index: 10;\n transform: translate(-50%, -50%) scale(1.45);\n box-shadow:\n 0 0 8px currentColor,\n 0 0 16px currentColor;\n}\n\n/* Farben nach Status */\n.seat.frei {\n background: #22c55e; /* Grün */\n color: #22c55e;\n}\n\n.seat.belegt {\n background: #ef4444; /* Rot */\n color: #ef4444;\n}\n\n.seat.selected {\n outline: 3px solid #2563eb;\n outline-offset: 3px;\n}\n\n/* Legende */\n#legend {\n display: flex;\n flex-wrap: wrap;\n gap: 14px;\n margin-top: 8px;\n color: #374151;\n font-size: 13px;\n}\n\n.legendItem {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n}\n\n.legendColor {\n display: inline-block;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n}\n\n.legendColor.frei {\n background: #22c55e;\n}\n\n.legendColor.belegt {\n background: #ef4444;\n}", "html": "
updateModel({\n selectedSeat: seat,\n calendarView: \"dayGridMonth\",\n selectedDate: null,\n calendarMode: \"seat\"\n});function getModel() {\n const model = appsmith.model || {};\n\n if (typeof model === \"string\") {\n try {\n return JSON.parse(model);\n } catch (error) {\n return {};\n }\n }\n\n return model;\n}\n\n/**\n * 40 statische Plätze erzeugen (Fallback, wenn noch keine DB-Daten)\n */\nfunction createDefaultSeats() {\n const seats = [];\n let number = 1;\n\n const columns = 8; // 8 x 5 = 40\n const rows = 5;\n\n for (let row = 0; row < rows; row++) {\n for (let column = 0; column < columns; column++) {\n seats.push({\n id: `P${String(number).padStart(2, \"0\")}`,\n name: `Platz P${String(number).padStart(2, \"0\")}`,\n x: 10 + column * 11.0, // X in Prozent\n y: 15 + row * 17.0, // Y in Prozent\n status: \"frei\"\n });\n number++;\n }\n }\n\n return seats;\n}\n\n/**\n * Plätze aus dem Model lesen, ansonsten statische 40 zurückgeben\n */\nfunction getSeats() {\n const model = getModel();\n\n let seats = model.seats;\n\n if (typeof seats === \"string\") {\n try {\n seats = JSON.parse(seats);\n } catch (error) {\n seats = [];\n }\n }\n\n if (seats && Array.isArray(seats.data)) {\n seats = seats.data;\n }\n\n if (!Array.isArray(seats) || seats.length === 0) {\n // Fallback: 40 statische Plätze\n return createDefaultSeats();\n }\n\n return seats;\n}\n\n/**\n * Status-Text für Anzeige\n */\nfunction getStatusText(status) {\n const s = String(status || \"\").trim().toLowerCase();\n\n if (s === \"belegt\" || s === \"gebucht\" || s === \"booked\" || s === \"occupied\") {\n return \"Belegt\";\n }\n\n return \"Frei\";\n}\n\nfunction updateSelectedSeatText() {\n const textElement = document.getElementById(\"selectedSeatText\");\n if (!textElement) return;\n\n if (!selectedSeat) {\n textElement.textContent = \"Kein Platz ausgewählt\";\n return;\n }\n\n textElement.textContent =\n `${selectedSeat.name} – Status: ${getStatusText(selectedSeat.status)}`;\n}\n\nfunction selectSeat(seat) {\n const status = String(seat.status || \"frei\")\n .trim()\n .toLowerCase();\n\n if (status !== \"frei\" && status !== \"available\") {\n return;\n }\n\n selectedSeat = seat;\n\n appsmith.updateModel({\n selectedSeat: seat\n });\n\n updateSelectedSeatText();\n renderSeats();\n\n try {\n appsmith.triggerEvent(\"seatSelected\");\n } catch (error) {\n // Event evtl. noch nicht konfiguriert\n }\n}\n\n/**\n * Auswahl löschen\n */\nfunction clearSelection() {\n selectedSeat = null;\n\n appsmith.updateModel({\n selectedSeat: null\n });\n\n updateSelectedSeatText();\n renderSeats();\n}\n\n/**\n * Button (Punkt) für einen Platz erzeugen\n */\nfunction createSeatButton(seat) {\n const button = document.createElement(\"button\");\n\n button.type = \"button\";\n button.classList.add(\"seat\");\n\n const s = String(seat.status || \"frei\").trim().toLowerCase();\n const isBooked =\n s === \"belegt\" ||\n s === \"gebucht\" ||\n s === \"booked\" ||\n s === \"occupied\";\n\n if (isBooked) {\n button.classList.add(\"belegt\");\n } else {\n button.classList.add(\"frei\");\n }\n\n if (selectedSeat && String(selectedSeat.id) === String(seat.id)) {\n button.classList.add(\"selected\");\n }\n\n button.style.left = `${Number(seat.x)}%`;\n button.style.top = `${Number(seat.y)}%`;\n\n button.title = `${seat.name} – ${getStatusText(seat.status)}`;\n\n button.addEventListener(\"click\", () => {\n selectSeat(seat);\n });\n\n return button;\n}\n\n/**\n * Plätze auf dem Bild rendern\n */\nfunction renderSeats() {\n const layer = document.getElementById(\"seatLayer\");\n const image = document.getElementById(\"floorplanImage\");\n\n if (!layer || !image) {\n return;\n }\n\n // Bild per Base64 setzen\n image.src = FLOORPLAN_IMAGE;\n\n // Punkte neu zeichnen\n layer.innerHTML = \"\";\n\n const seats = getSeats();\n\n seats.forEach((seat) => {\n layer.appendChild(createSeatButton(seat));\n });\n\n updateSelectedSeatText();\n}\n\n/**\n * Events registrieren\n */\nfunction registerUiEvents() {\n const clearButton = document.getElementById(\"clearSelectionButton\");\n\n if (clearButton) {\n clearButton.addEventListener(\"click\", clearSelection);\n }\n}\n\n/**\n * Einstiegspunkt für das Custom Widget\n */\nappsmith.onReady(() => {\n registerUiEvents();\n renderSeats();\n\n appsmith.onModelChange(() => {\n renderSeats();\n });\n});\n\nwo füge ich das ein?" }, "version": 1, "widgetId": "londvdt302", "widgetName": "Custom4" }