- | Foto |
- Marke / Modell |
- € / Tag |
- Aktiv |
+ Foto |
+ Marke / Modell |
+ € / Tag |
+ Aktiv |
|
diff --git a/frontend/admin.js b/frontend/admin.js
index 6e6ef0d..1a81c6a 100644
--- a/frontend/admin.js
+++ b/frontend/admin.js
@@ -1,4 +1,5 @@
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.45.4";
+import { getLang, setLang, t, applyI18n } from "./i18n.js";
const SUPA_URL = window.MCCARS_CONFIG?.SUPABASE_URL ?? "";
const SUPA_KEY = window.MCCARS_CONFIG?.SUPABASE_ANON_KEY || "";
@@ -12,6 +13,7 @@ const supabase = createClient(SUPA_URL, SUPA_KEY, {
// ----- DOM -----
const loginView = document.querySelector("#loginView");
const adminView = document.querySelector("#adminView");
+const langToggle = document.querySelector(".lang-toggle");
const rotateView = document.querySelector("#rotateView");
const loginForm = document.querySelector("#loginForm");
const loginError = document.querySelector("#loginError");
@@ -59,6 +61,7 @@ const state = {
// AUTH FLOW
// =========================================================================
async function bootstrap() {
+ applyI18n();
const { data: { session } } = await supabase.auth.getSession();
if (session) {
// Always fetch fresh user from server so metadata (must_change_password) is current.
@@ -204,8 +207,8 @@ function renderVehicles() {
€ ${v.daily_price_eur} |
${v.is_active ? "✅" : "—"} |
-
-
+
+
| `;
tableBody.appendChild(tr);
}
@@ -352,12 +355,12 @@ function renderLeads() {
${esc(l.date_from || "—")} → ${esc(l.date_to || "—")} |
${esc(l.status)} |
-
+
${wantActive ? `
-
-
+
+
` : `
-
+
`}
| `;
leadsTableBody.appendChild(tr);
@@ -385,9 +388,9 @@ function openLead(id) {
${l.is_active ? `
-
-
- ` : ``}
+
+
+ ` : ``}
`;
leadDialog.showModal();
const note = () => document.querySelector("#leadNote").value;
@@ -448,7 +451,7 @@ function renderCustomers() {
${esc(c.status)} |
| `;
customersTableBody.appendChild(tr);
@@ -493,4 +496,18 @@ function fmtDate(iso) {
return d.toLocaleString("de-AT", { dateStyle: "short", timeStyle: "short" });
}
+if (langToggle) {
+ langToggle.addEventListener("click", () => {
+ const current = getLang();
+ setLang(current === "de" ? "en" : "de");
+ langToggle.textContent = getLang() === "de" ? "EN" : "DE";
+ applyI18n();
+ // Re-render JS injected text correctly
+ if (state.vehicles) renderVehicles();
+ if (state.leads) renderLeads();
+ if (state.customers) renderCustomers();
+ });
+ langToggle.textContent = getLang() === "de" ? "EN" : "DE";
+}
+
bootstrap();
diff --git a/frontend/i18n.js b/frontend/i18n.js
index afa159d..e0d8687 100644
--- a/frontend/i18n.js
+++ b/frontend/i18n.js
@@ -69,11 +69,57 @@ export const translations = {
footerNav: "Navigation",
imprint: "Impressum",
privacy: "Datenschutz",
- terms: "Mietbedingungen",
+ footerTerms: "Mietbedingungen",
copyright: "Alle Rechte vorbehalten.",
close: "Schliessen",
editVehicle: "Fahrzeug bearbeiten",
+
+ adminNavWebsite: "Website",
+ adminChangePw: "Passwort aendern",
+ adminLogout: "Logout",
+ adminLeads: "Leads",
+ adminCustomers: "Kunden",
+ adminVehicles: "Fahrzeuge",
+ adminNewVehicle: "Neues Fahrzeug",
+ adminAllVehicles: "Alle Fahrzeuge",
+ adminPhotoUpload: "Foto hochladen (JPG/PNG/WebP, max 50 MB)",
+ adminPhotoUrl: "Foto-URL (wird automatisch gesetzt nach Upload)",
+ adminBrand: "Marke",
+ adminModel: "Modell",
+ adminPower: "PS",
+ adminSpeed: "Top-Speed km/h",
+ adminAccel: "0-100",
+ adminSeats: "Sitze",
+ adminPrice: "Preis / Tag (€)",
+ adminSort: "Reihenfolge",
+ adminLocation: "Standort",
+ adminDescDe: "Beschreibung (Deutsch)",
+ adminDescEn: "Description (English)",
+ adminActiveVisible: "Aktiv / auf Website sichtbar",
+ adminSave: "Speichern",
+ adminReset: "Neu",
+ adminPhoto: "Foto",
+ adminBrandTable: "Marke / Modell",
+ adminPriceTable: "€ / Tag",
+ adminActive: "Aktiv",
+ adminDel: "Löschen",
+ adminQualify: "Qualifizieren",
+ adminReject: "Ablehnen",
+ adminReopen: "Wieder öffnen",
+ adminDetails: "Details",
+ adminSetInactive: "Inaktiv setzen",
+ adminSetActive: "Aktiv setzen",
+ adminActiveLeads: "Aktive Leads",
+ adminClosedLeads: "Abgeschlossen",
+ adminSourceLead: "Quelle (Lead)",
+ adminFirstContact: "Erster Kontakt",
+ adminNameEmail: "Name / E-Mail",
+ adminPhone: "Telefon",
+ adminStatus: "Status",
+ adminReceived: "Eingang",
+ adminVehicleTab: "Fahrzeug",
+ adminPeriod: "Zeitraum",
},
en: {
navCars: "Fleet",
@@ -144,11 +190,57 @@ export const translations = {
footerNav: "Navigation",
imprint: "Imprint",
privacy: "Privacy",
- terms: "Rental conditions",
+ footerTerms: "Rental conditions",
copyright: "All rights reserved.",
close: "Close",
editVehicle: "Edit vehicle",
+
+ adminNavWebsite: "Website",
+ adminChangePw: "Change password",
+ adminLogout: "Logout",
+ adminLeads: "Leads",
+ adminCustomers: "Customers",
+ adminVehicles: "Vehicles",
+ adminNewVehicle: "New vehicle",
+ adminAllVehicles: "All vehicles",
+ adminPhotoUpload: "Upload photo (JPG/PNG/WebP, max 50 MB)",
+ adminPhotoUrl: "Photo URL (auto-set after upload)",
+ adminBrand: "Brand",
+ adminModel: "Model",
+ adminPower: "HP",
+ adminSpeed: "Top speed km/h",
+ adminAccel: "0-62",
+ adminSeats: "Seats",
+ adminPrice: "Price / day (€)",
+ adminSort: "Sort order",
+ adminLocation: "Location",
+ adminDescDe: "Description (German)",
+ adminDescEn: "Description (English)",
+ adminActiveVisible: "Active / visible on website",
+ adminSave: "Save",
+ adminReset: "New",
+ adminPhoto: "Photo",
+ adminBrandTable: "Brand / Model",
+ adminPriceTable: "€ / day",
+ adminActive: "Active",
+ adminDel: "Delete",
+ adminQualify: "Qualify",
+ adminReject: "Reject",
+ adminReopen: "Reopen",
+ adminDetails: "Details",
+ adminSetInactive: "Set inactive",
+ adminSetActive: "Set active",
+ adminActiveLeads: "Active leads",
+ adminClosedLeads: "Closed",
+ adminSourceLead: "Source (Lead)",
+ adminFirstContact: "First contact",
+ adminNameEmail: "Name / Email",
+ adminPhone: "Phone",
+ adminStatus: "Status",
+ adminReceived: "Received",
+ adminVehicleTab: "Vehicle",
+ adminPeriod: "Period",
},
};
diff --git a/frontend/styles.css b/frontend/styles.css
index edd6559..5ce9ad0 100644
--- a/frontend/styles.css
+++ b/frontend/styles.css
@@ -604,6 +604,49 @@ table.admin-table tbody tr:hover {
filter: brightness(1.1);
}
+/* ---------------- Forms / Toggle Switch ---------------- */
+.toggle-switch {
+ position: relative;
+ display: inline-block;
+ width: 44px;
+ height: 24px;
+ flex-shrink: 0;
+}
+.toggle-switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+.toggle-slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0; left: 0; right: 0; bottom: 0;
+ background-color: var(--line);
+ transition: background-color 0.3s ease;
+ border-radius: 24px;
+}
+.toggle-slider:before {
+ position: absolute;
+ content: "";
+ height: 16px;
+ width: 16px;
+ left: 4px;
+ bottom: 4px;
+ background-color: var(--text);
+ transition: transform 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
+ border-radius: 50%;
+}
+input:checked + .toggle-slider {
+ background-color: var(--accent);
+}
+input:focus + .toggle-slider {
+ box-shadow: 0 0 0 2px var(--bg-card), 0 0 0 4px var(--accent);
+}
+input:checked + .toggle-slider:before {
+ transform: translateX(20px);
+ background-color: #111;
+}
+
/* Admin tabs */
.admin-tabs {
display: flex; gap: 0.4rem;