<!-- 1) Flatpickr CSS (CDN) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css" />
<style>
/* Keep your existing styles plus slight additions for timeslots */
.separator {
border: none !important;
height: 2px !important;
background-color: var(--primary-color) !important;
width: 100% !important;
margin: 20px auto !important;
}
/* Loader placeholder */
#content::before {
min-height: 300px;
content: "Indlæser...";
display: block;
text-align: center;
color: #aaa;
font-style: italic;
padding-top: 2rem;
}
.content-loaded::before {
content: none !important;
}
/* Search Bar Container */
.search-bar-container {
position: relative;
display: flex;
align-items: center;
max-width: 100%;
margin: 10px 0;
}
/* Search Icon */
#search-icon {
position: absolute;
right: 15px;
font-size: 16px;
color: #666;
pointer-events: none;
background: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2024%2024%22%20fill=%22none%22%20stroke=%22%2333658A%22%20stroke-width=%223%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%20class=%22icon%20icon-search%22%3E%3Ccircle%20cx=%2210%22%20cy=%2210%22%20r=%227%22%3E%3C/circle%3E%3Cline%20x1=%2216%22%20y1=%2216%22%20x2=%2221%22%20y2=%2221%22%3E%3C/line%3E%3C/svg%3E') no-repeat center;
background-size: 16px 16px;
height: 100%;
width: 20px;
}
/* Search Input */
#device-search-bar {
width: 100%;
padding: 10px 40px 10px 15px;
border: 1px solid var(--primary-color);
border-radius: 8px;
font-size: 16px;
outline: none;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
font-family: var(--font-family);
color: var(--secondary-color);
}
#device-search-bar:focus {
border-color: #007bff;
box-shadow: 0 0 5px rgba(47, 102, 152, 0.5);
}
#header h1 {
font-size: 26px;
color: var(--primary-color);
margin-bottom: 10px;
}
h3 {
font-size: 16px;
color: var(--primary-color);
}
/* General Widget Styles */
#repair-widget {
/* CSS Variables for common values */
--primary-color: #33658A;
--secondary-color: #12375A;
--accent-color: #FADF63;
--hover-color: #2F6699;
--border-color: #ddd;
--background-light: #ffffff;
--background-muted: #f9f9f9;
--font-family: 'Poppins', sans-serif;
font-family: var(--font-family);
max-width: 1200px;
padding: 20px;
margin: 0 auto; /* This centers the container */
border-radius: 12px 12px 12px 12px;
box-shadow: 0px 8px 20px rgba(0, 0, 0, 0.1);
background-color: var(--background-light);
margin-top: 20px;
}
/* Headings */
#repair-widget h1, #repair-widget h2, #repair-widget h3 {
font-family: var(--font-family);
color: var(--secondary-color);
}
#repair-header {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 20px;
}
.device-header {
display: flex;
align-items: flex-end;
gap: 10px;
margin-bottom: 20px;
}
.device-thumbnail {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 8px;
border: 1px solid #ddd;
}
.device-info {
font-size: 26px;
font-weight: bold;
color: var(--secondary-color);
overflow-wrap: break-word;
}
.device-info p {
margin: 0;
font-size: 14px;
color: #666;
}
/* Breadcrumb Styles */
#breadcrumb {
font-size: 12px;
color: var(--primary-color);
}
.breadcrumb-step {
cursor: pointer;
color: var(--primary-color);
text-decoration: none;
}
.breadcrumb-step:hover {
text-decoration: underline;
}
/* Dropdown Styles */
#dropdown-results {
position: absolute;
width: 100%;
max-height: 300px;
overflow-y: auto;
background-color: var(--background-light);
border: 1px solid var(--border-color);
border-radius: 8px;
z-index: 10;
}
.dropdown-item {
display: flex;
align-items: center;
padding: 10px;
cursor: pointer;
transition: background 0.2s ease;
}
.dropdown-item:hover {
background-color: #f1f1f1;
}
.dropdown-image {
width: 40px;
height: 40px;
object-fit: cover;
border-radius: 50%;
margin-right: 10px;
}
.dropdown-text {
font-size: 14px;
color: #333;
}
/* Content Grid */
#content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
/* Card Styles */
.card {
border: 1px solid var(--border-color);
border-radius: 10px;
overflow: hidden;
cursor: pointer;
text-align: center;
transition: all 0.3s ease;
height: 300px;
max-height: 300px;
pointer-events: auto;
display: flex;
flex-direction: column; /* stack image above title */
justify-content: center; /* center vertically */
align-items: center; /* center horizontally */
}
.card:hover {
transform: scale(1.05);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
.card-image {
width: 100%;
height: 200px;
max-height: 200px;
object-fit: contain;
border-radius: 10px;
padding: 10px;
}
.card-title {
padding: 0px 6px 0px 6px;
font-size: 16px;
color: var(--primary-color);
text-align: center;
display: block;
}
/* Responsive Design */
@media (max-width: 768px) {
.device-info {
font-size: 20px;
}
#repair-widget {
padding: 15px;
margin-top: 0px;
border-radius: 0px 0px 0px 0px;
}
.card {
width: 100%;
height: auto;
}
.card-image {
height: 150px;
padding: 5px;
}
.card-title {
font-size: 14px;
}
}
@media (max-width: 480px) {
.card {
border: 1px solid var(--border-color);
border-radius: 4px;
overflow: hidden;
cursor: pointer;
text-align: center;
transition: all 0.3s ease;
height: 170px;
max-height: 170px;
pointer-events: auto;
display: flex;
flex-direction: column; /* stack image above title */
justify-content: center; /* center vertically */
align-items: center; /* center horizontally */
}
.card:hover {
transform: scale(1.05);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
.card-image {
width: 100%;
height: 100px;
max-height: 100px;
object-fit: contain;
border-radius: 4px;
padding: 4px;
}
.card-title {
padding: 10px 0;
font-size: 12px;
color: var(--primary-color);
text-align: center;
display: block;
}
#content {
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
justify-content: center;
gap: 10px;
}
}
/* Problem List & Cards */
.problem-list {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 2px;
width: 100%;
}
@media (max-width: 768px) {
.problem-list {
grid-auto-flow: unset;
grid-template-columns: 1fr;
}
}
.problem-card {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 8px;
background-color: var(--background-light);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
width: calc(100%);
cursor: pointer;
position: relative;
z-index: 10;
}
.problem-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
z-index: 20;
}
.problem-card.selected {
background-color: #e6f7ff;
border-left: 4px solid var(--primary-color);
}
.problem-info {
flex: 1;
margin-right: 16px;
}
.problem-name {
font-size: 16px;
font-weight: bold;
color: var(--secondary-color);
margin: 0;
display: flex;
align-items: center;
gap: 6px;
}
.problem-action {
position: absolute;
top: 12px;
right: 12px;
}
.problem-price {
font-size: 14px;
font-weight: bold;
color: var(--primary-color);
white-space: nowrap;
}
/* Tooltip */
.info-icon::after {
content: attr(data-tooltip);
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%);
max-width: 80vw;
background-color: #333;
color: #fff;
font-size: 12px;
padding: 5px 8px;
border-radius: 4px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
visibility: hidden;
opacity: 0;
transition: opacity 0.2s ease, visibility 0.2s ease;
z-index: 9999;
text-align: center;
word-wrap: break-word;
}
.info-icon.show-tooltip::after,
.info-icon:hover::after {
visibility: visible;
opacity: 1;
}
@media (max-width: 768px) {
.info-icon::after {
bottom: auto;
left: 50%;
transform: translateX(-50%) translateY(20px);
min-width: 250px;
max-width: 80vw;
font-size: 12px;
padding: 8px 10px;
z-index: 9999;
}
}
/* Booking Form Container */
.booking-form-container {
padding: 4px;
border: 0px solid var(--border-color);
border-radius: 8px;
background-color: var(--background-light);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.0);
}
#booking-form {
display: flex;
flex-direction: column;
gap: 10px;
}
#booking-form textarea,
#booking-form input {
font-family: var(--font-family);
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
box-sizing: border-box;
color: var(--secondary-color);
}
#booking-form textarea {
resize: none;
height: 100px;
}
#booking-form button {
padding: 12px;
font-size: 16px;
font-weight: bold;
font-family: var(--font-family);
color: var(--accent-color);
background-color: var(--secondary-color);
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease;
}
#booking-form button:hover {
background-color: var(--hover-color);
color: var(--accent-color);
}
@media (max-width: 768px) {
.booking-form-container {
padding: 15px;
}
#booking-form button {
font-size: 14px;
}
}
#problem-summary {
padding: 15px;
border: 1px solid var(--border-color);
border-radius: 8px;
background-color: var(--background-muted);
font-size: 14px;
color: var(--secondary-color);
white-space: pre-wrap;
overflow-y: auto;
max-height: 200px;
margin-bottom: 20px;
}
/* Timeslot UI */
.timeslot-container {
margin-top: 10px;
display: none; /* shown once a date is selected */
}
.timeslot-heading {
font-size: 14px;
color: var(--primary-color);
margin-bottom: 10px;
}
.timeslot-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(70px, 1fr));
gap: 10px;
margin-bottom: 10px;
}
#repair-widget #booking-form .timeslot-btn {
padding: 8px;
border: 1px solid var(--border-color);
border-radius: 4px;
background-color: var(--background-light);
cursor: pointer;
text-align: center;
transition: background-color 0.2s ease, box-shadow 0.2s ease;
font-size: 14px;
color: var(--secondary-color);
}
.timeslot-btn:hover {
background-color: var(--background-muted);
}
#repair-widget #booking-form .timeslot-btn.selected {
background-color: #e6f7ff;
border-color: var(--primary-color);
}
.timeslot-btn.booked {
display: none !important;
}
.walk-in-info-box {
background-color: #fff8dc; /* light, warm background */
border: 1px solid #f0e68c; /* subtle border color */
padding: 10px;
margin: 10px auto; /* space above and below */
font-size: 16px;
color: #333;
text-align: left;
border-radius: 8px;
}
/* Improved Back Button Styling */
#back-button {
background-color: transparent;
border: none;
outline: none;
cursor: pointer;
display: flex;
align-items: center;
transition: transform 0.2s, opacity 0.2s;
}
#back-button svg {
stroke: var(--primary-color);
transition: stroke 0.2s;
}
#back-button:hover svg {
stroke: #285177;
}
/* Responsive Handling */
@media (max-width: 768px) {
#back-button svg {
width: 18px;
height: 18px;
}
}
/* Breadcrumb Styling */
#breadcrumb-container {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
justify-content: flex-start; /* This ensures left alignment */
}
#breadcrumb-container #breadcrumb {
font-size: 14px;
color: var(--primary-color);
}
.breadcrumb-step {
cursor: pointer;
text-decoration: none;
color: var(--primary-color);
}
.breadcrumb-step:hover {
text-decoration: underline;
}
.booking-heading {
margin-bottom: 10px;
}
</style>
<div id="repair-widget">
<!-- Breadcrumb Container with Back Button -->
<div id="breadcrumb-container" style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
<button id="back-button" style="display: none;" aria-label="Tilbage">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</button>
<div id="breadcrumb"></div>
</div>
<div id="header">
<h1>Slå din pris op og book tid</h1>
<div class="search-bar-container">
<input type="text" id="device-search-bar" placeholder="Søg efter din enhed..." autocomplete="off" data-hj-allow />
<button id="clear-search-bar" class="hidden">X</button>
<span id="search-icon"></span>
</div>
<div id="dropdown-results" class="hidden"></div>
</div>
<div id="content"></div>
</div>
<!-- 2) Flatpickr JS (CDN) -->
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
/* ========================================
PART 1: Existing Widget Initialization
=========================================*/
const repairWidget = document.getElementById("repair-widget");
const main = document.getElementById("main");
const breadcrumbDiv = document.getElementById("breadcrumb");
const headerH1 = document.querySelector("#header h1");
const contentDiv = document.getElementById("content");
const searchBar = document.getElementById("device-search-bar");
const dropdownResults = document.getElementById("dropdown-results");
const clearSearchButton = document.getElementById("clear-search-bar");
// --- SEARCH ICON TOGGLE CODE ---
const searchIcon = document.getElementById("search-icon");
const originalIconURL = 'data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2024%2024%22%20fill=%22none%22%20stroke=%22%2333658A%22%20stroke-width=%223%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%20class=%22icon%20icon-search%22%3E%3Ccircle%20cx=%2210%22%20cy=%2210%22%20r=%227%22%3E%3C/circle%3E%3Cline%20x1=%2216%22%20y1=%2216%22%20x2=%2221%22%20y2=%2221%22%3E%3C/line%3E%3C/svg%3E';
const clearIconURL = 'data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2024%2024%22%20fill=%22none%22%20stroke=%22%2333658A%22%20stroke-width=%222%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%3E%3Cline%20x1=%2218%22%20y1=%226%22%20x2=%226%22%20y2=%2218%22/%3E%3Cline%20x1=%226%22%20y1=%226%22%20x2=%2218%22%20y2=%2218%22/%3E%3C/svg%3E';
searchBar.addEventListener("input", function() {
if (this.value.trim().length > 0) {
// Change only the background-image property so the original CSS background-size remains.
searchIcon.style.backgroundImage = `url("${clearIconURL}")`;
searchIcon.style.pointerEvents = "auto";
} else {
searchIcon.style.backgroundImage = `url("${originalIconURL}")`;
searchIcon.style.pointerEvents = "none";
dropdownResults.classList.add("hidden");
}
});
searchIcon.addEventListener("click", function() {
if (searchBar.value.trim().length > 0) {
searchBar.value = "";
searchIcon.style.backgroundImage = `url("${originalIconURL}")`;
searchIcon.style.pointerEvents = "none";
dropdownResults.classList.add("hidden");
searchBar.dispatchEvent(new Event("input"));
}
});
// Clear search when ESC is pressed
document.addEventListener("keydown", function(e) {
if (e.key === "Escape") {
if (searchBar.value.trim().length > 0) {
searchBar.value = "";
searchIcon.style.backgroundImage = `url("${originalIconURL}")`;
searchIcon.style.pointerEvents = "none";
dropdownResults.classList.add("hidden");
searchBar.dispatchEvent(new Event("input", { bubbles: true }));
//console.log("Search cleared via ESC");
}
}
});
// Clear search if user clicks outside the search container
document.addEventListener("click", function(e) {
if (!e.target.closest(".search-bar-container")) {
if (searchBar.value.trim().length > 0) {
searchBar.value = "";
searchIcon.style.backgroundImage = `url("${originalIconURL}")`;
searchIcon.style.pointerEvents = "none";
dropdownResults.classList.add("hidden");
searchBar.dispatchEvent(new Event("input", { bubbles: true }));
//console.log("Search cleared via outside click");
}
}
if (!e.target.closest(".info-icon")) {
document.querySelectorAll(".info-icon").forEach((icon) => {
icon.classList.remove("show-tooltip");
});
}
});
let jsonUrl = "something";
let breadcrumb = [];
let allDevices = [];
let firstLoad = true;
let debounceTimeout;
function debounce(func, delay) {
return function (...args) {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
async function getAttachedJsonUrl(postId) {
try {
const response = await fetch(
`https://www.techdoktoren.dk/wp-json/wp/v2/media?parent=${postId}`
);
if (!response.ok) {
throw new Error(`Failed to fetch media: ${response.status}`);
}
const mediaFiles = await response.json();
const jsonFile = mediaFiles.find(
(file) => file.mime_type === "application/json"
);
if (jsonFile) return jsonFile.source_url;
throw new Error("No JSON file found attached to this post.");
} catch (error) {
console.error("Error fetching attached JSON file:", error);
return null;
}
}
async function initWidget() {
const attachedJsonUrl = await getAttachedJsonUrl(9813);
if (attachedJsonUrl) {
jsonUrl = attachedJsonUrl;
//console.log("JSON URL found:", jsonUrl);
try {
const response = await fetch(jsonUrl);
const data = await response.json();
allDevices = extractDevices(data.data);
contentDiv.classList.add("content-loaded");
//Check URL params
const urlParams = new URLSearchParams(window.location.search);
const categoryParam = urlParams.get("category");
const manufacturerParam = urlParams.get("manufacturer");
const deviceParam = urlParams.get("device");
navigateTo("Categories", data.data);
if (categoryParam) {
const foundCategory = data.data.find(cat => cat.name === categoryParam);
if (foundCategory) {
selectedCategory = foundCategory;
navigateTo("Manufacturers", foundCategory.manufacturers);
if (manufacturerParam) {
const foundManufacturer = foundCategory.manufacturers.find(man => man.name === manufacturerParam);
if (foundManufacturer) {
selectedManufacturer = foundManufacturer;
navigateTo("Devices", foundManufacturer.devices);
if (deviceParam) {
const foundDevice = foundManufacturer.devices.find(dev => dev.name === deviceParam);
if (foundDevice) {
navigateTo("Problems", foundDevice.problems, foundDevice);
}
}
}
}
}
}
} catch (error) {
console.error("Error fetching JSON data:", error);
contentDiv.classList.add("content-loaded");
contentDiv.innerText = "Failed to load data.";
}
} else {
//console.log("No JSON file found. Cannot fetch data.");
contentDiv.classList.add("content-loaded");
contentDiv.innerText = "No data available.";
}
}
function extractDevices(categories) {
const devices = [];
categories.forEach((category) => {
category.manufacturers.forEach((manufacturer) => {
manufacturer.devices.forEach((device) => {
devices.push({
name: device.name,
image: device.image,
problems: device.problems,
hierarchy: [category.name, manufacturer.name, device.name],
category,
manufacturer,
});
});
});
});
return devices;
}
function attachTooltipHandler(infoIcon) {
infoIcon.addEventListener("click", (e) => {
e.stopPropagation();
document.querySelectorAll(".info-icon").forEach((icon) => {
if (icon !== infoIcon) {
icon.classList.remove("show-tooltip");
}
});
infoIcon.classList.toggle("show-tooltip");
const tooltipRect = infoIcon.getBoundingClientRect();
const viewportWidth = window.innerWidth;
if (tooltipRect.right > viewportWidth) {
infoIcon.style.setProperty("--tooltip-left", `${viewportWidth - tooltipRect.right}px`);
} else {
infoIcon.style.removeProperty("--tooltip-left");
}
});
}
function highlightSearchTerms(text, term) {
const terms = term.split(" ").filter(Boolean);
const regex = new RegExp(`(${terms.join("|")})`, "gi");
return text.replace(regex, (match) => `<mark>${match}</mark>`);
}
function renderDropdownResults(devices) {
dropdownResults.innerHTML = "";
if (devices.length === 0) {
dropdownResults.innerHTML = `<div class="dropdown-item">Ingen enhed fundet med din søgning.</div>`;
dropdownResults.classList.remove("hidden");
return;
}
devices.forEach((device) => {
const highlightedName = highlightSearchTerms(device.name, searchBar.value);
const item = document.createElement("div");
item.className = "dropdown-item";
item.innerHTML = `
<img src="${
device.image || "https://www.techdoktoren.dk/wp-content/uploads/2024/12/device.png"
}" class="dropdown-image" alt="${device.name}" />
<span class="dropdown-text">${highlightedName}</span>
`;
item.addEventListener("click", () => {
//console.log("Device Selected:", device);
// Clear search input and hide dropdown
searchBar.value = "";
dropdownResults.classList.add("hidden");
// **Set the state explicitly**
selectedCategory = device.category;
selectedManufacturer = device.manufacturer;
navigateTo("Manufacturers", device.category.manufacturers);
navigateTo("Devices", device.manufacturer.devices);
navigateTo("Problems", device.problems, device);
});
dropdownResults.appendChild(item);
});
dropdownResults.classList.remove("hidden");
}
function handleNormalSearch(e) {
const searchTerm = e.target.value.toLowerCase().trim();
if (!searchTerm) {
dropdownResults.classList.add("hidden");
dropdownResults.innerHTML = "";
return;
}
// Split the search term into words
const searchTerms = searchTerm.split(" ").filter(Boolean);
const matchingDevices = allDevices.filter((device) => {
// Combine manufacturer and device name into one string
const combinedText = `${device.manufacturer.name} ${device.name}`.toLowerCase();
// Ensure every word in the search query is found in the combined text
return searchTerms.every(term => combinedText.includes(term));
});
renderDropdownResults(matchingDevices);
}
const debouncedNormalSearch = debounce(handleNormalSearch, 300);
function handleFilterSearch(e) {
const searchTerm = e.target.value.toLowerCase().trim();
const currentLevel = breadcrumb[breadcrumb.length - 1];
if (!searchTerm) {
renderContent(currentLevel.level, currentLevel.items);
return;
}
// Split the search term into words
const searchTerms = searchTerm.split(" ").filter(Boolean);
const filteredItems = currentLevel.items.filter(item => {
// If the item has a manufacturer (for device level), combine both fields.
let combinedText = "";
if (item.manufacturer && item.name) {
combinedText = `${item.manufacturer.name} ${item.name}`.toLowerCase();
} else {
combinedText = item.name.toLowerCase();
}
// Ensure every search term is present in the combined text
return searchTerms.every(term => combinedText.includes(term));
});
renderContent(currentLevel.level, filteredItems);
}
const debouncedFilterSearch = debounce(handleFilterSearch, 300);
function updateURL(level, currentDevice = null) {
const urlParams = new URLSearchParams(window.location.search);
// Update URL based on the current level
if (level === "Categories") {
urlParams.delete("category");
urlParams.delete("manufacturer");
urlParams.delete("device");
urlParams.delete("problem");
} else if (level === "Manufacturers" && selectedCategory) {
urlParams.set("category", selectedCategory.name);
urlParams.delete("manufacturer");
urlParams.delete("device");
urlParams.delete("problem");
} else if (level === "Devices" && selectedCategory && selectedManufacturer) {
urlParams.set("category", selectedCategory.name);
urlParams.set("manufacturer", selectedManufacturer.name);
urlParams.delete("device");
urlParams.delete("problem");
} else if (level === "Problems" && currentDevice) {
urlParams.set("category", selectedCategory.name);
urlParams.set("manufacturer", selectedManufacturer.name);
urlParams.set("device", currentDevice.name);
}
// Push the new URL state to the browser's history
const newURL = window.location.pathname + "?" + urlParams.toString();
window.history.pushState({}, "", newURL);
}
function navigateTo(level, items, device = null) {
breadcrumb.push({ level, items });
renderBreadcrumb();
renderContent(level, items, device);
if (device) {
headerH1.textContent = "";
} else {
headerH1.textContent = "Slå din pris op og book tid";
}
contentDiv.style.display = level === "Problems" ? "block" : "grid";
if (level === "Categories") {
searchBar.style.display = "block";
searchBar.placeholder = "Søg efter din enhed";
dropdownResults.classList.add("hidden");
searchBar.removeEventListener("input", debouncedFilterSearch);
searchBar.addEventListener("input", debouncedNormalSearch);
} else if (level === "Manufacturers" || level === "Devices") {
searchBar.style.display = "block";
searchBar.placeholder =
level === "Manufacturers" ? "Filtrer efter mærke..." : "Filtrer efter model...";
dropdownResults.classList.add("hidden");
searchBar.removeEventListener("input", debouncedNormalSearch);
searchBar.addEventListener("input", debouncedFilterSearch);
} else {
searchBar.style.display = "none";
searchBar.removeEventListener("input", debouncedFilterSearch);
searchBar.removeEventListener("input", debouncedNormalSearch);
dropdownResults.classList.add("hidden");
}
if (!firstLoad) {
main.scrollIntoView({ behavior: "smooth" });
}
firstLoad = false;
updateURL(level, device);
updateBackButton(); // Ensure the back button visibility is updated whenever navigation happens
}
function navigateBreadcrumb(index) {
const breadcrumbStep = breadcrumb[index];
if (!breadcrumbStep.items) return;
if (index === breadcrumb.length - 1) return;
breadcrumb = breadcrumb.slice(0, index + 1);
renderBreadcrumb();
renderContent(breadcrumbStep.level, breadcrumbStep.items);
headerH1.textContent =
breadcrumbStep.level === "Problems" ? "" : "Slå din pris op og book tid";
searchBar.style.display = index === 0 ? "block" : "none";
contentDiv.style.display = "grid";
if (breadcrumbStep.level === "Categories") {
searchBar.style.display = "block";
searchBar.placeholder = "Søg efter din enhed";
dropdownResults.classList.add("hidden");
searchBar.removeEventListener("input", debouncedFilterSearch);
searchBar.addEventListener("input", debouncedNormalSearch);
} else if (breadcrumbStep.level === "Manufacturers" || breadcrumbStep.level === "Devices") {
searchBar.style.display = "block";
searchBar.placeholder =
breadcrumbStep.level === "Manufacturers" ? "Filtrer efter mærke..." : "Filtrer efter model...";
dropdownResults.classList.add("hidden");
searchBar.removeEventListener("input", debouncedNormalSearch);
searchBar.addEventListener("input", debouncedFilterSearch);
} else {
searchBar.style.display = "none";
dropdownResults.classList.add("hidden");
}
updateURL(breadcrumbStep.level);
}
const breadcrumbTranslations = {
Categories: "Kategorier",
Manufacturers: "Mærker",
Devices: "Modeller",
Problems: "Reparationer",
};
function renderBreadcrumb() {
updateBackButton();
breadcrumbDiv.innerHTML = breadcrumb
.map((step, index) => {
if (step.level === "Categories" && index === breadcrumb.length - 1) {
return "";
}
return `<span class="breadcrumb-step ${
step.items ? "" : "non-clickable"
}" data-index="${index}">${
breadcrumbTranslations[step.level] || step.level
}</span>`;
})
.filter(Boolean)
.join(" > ");
document.querySelectorAll(".breadcrumb-step").forEach((step) => {
step.addEventListener("click", () => {
const index = parseInt(step.getAttribute("data-index"), 10);
navigateBreadcrumb(index);
});
});
}
const backButton = document.getElementById("back-button");
function updateBackButton() {
if (breadcrumb.length > 1) {
backButton.style.display = "flex";
} else {
backButton.style.display = "none";
}
}
// Handle Back Button Click
backButton.addEventListener("click", () => {
if (breadcrumb.length > 1) {
const previousIndex = breadcrumb.length - 2; // Navigate to the previous level
navigateBreadcrumb(previousIndex);
}
});
/* ========================================
PART 2: Rendering the Content
=========================================*/
let selectedCategory = null;
let selectedManufacturer = null;
function renderContent(level, items, device = null) {
contentDiv.innerHTML = "";
if (level === "Categories") {
items.forEach((category) => {
const card = createCard(
category.name,
category.image || "default-category.jpg",
() => {
// Save the selected category
selectedCategory = category;
// Reset the manufacturer
selectedManufacturer = null;
navigateTo("Manufacturers", category.manufacturers);
}
);
contentDiv.appendChild(card);
});
} else if (level === "Manufacturers") {
items.forEach((manufacturer) => {
const card = createCard(
manufacturer.name,
manufacturer.image || "default-manufacturer.jpg",
() => {
// Save the selected manufacturer
selectedManufacturer = manufacturer;
navigateTo("Devices", manufacturer.devices);
}
);
contentDiv.appendChild(card);
});
} else if (level === "Devices") {
items.forEach((deviceItem) => {
const card = createCard(
deviceItem.name,
deviceItem.image || "https://www.techdoktoren.dk/wp-content/uploads/2024/12/device.png",
() => {
//console.log("Device Card Clicked:", deviceItem);
//console.log("Category:", selectedCategory);
//console.log("Manufacturer:", selectedManufacturer);
// Continue navigating to the next level:
navigateTo("Problems", deviceItem.problems, deviceItem);
}
);
contentDiv.appendChild(card);
});
}
/* ============= PROBLEMS + BOOKING FORM ============= */
if (level === "Problems" && device) {
generateSchemaMarkup(device.name, selectedManufacturer.name, items);
// Device header
const deviceHeader = document.createElement("div");
deviceHeader.className = "device-header";
deviceHeader.innerHTML = `
<img src="${
device.image || "https://www.techdoktoren.dk/wp-content/uploads/2024/12/device.png"
}" alt="${device.name}" class="device-thumbnail" />
<div class="device-info">${device.name} Reparation</div>
`;
contentDiv.appendChild(deviceHeader);
// Conditional information box for iPhone devices
if (device.name.toLowerCase().includes("iphone")) {
const infoBox = document.createElement("div");
infoBox.className = "walk-in-info-box";
infoBox.innerHTML = `
<strong>Bemærk:</strong> Vi tilbyder <em>walk-in reparation</em> uden booking, og de fleste iPhone reparationer tager under 60 minutter.
`;
contentDiv.appendChild(infoBox);
}
if (device.name.toLowerCase().includes("macbook")) {
const yearMatch = device.name.match(/\b(20\d{2})\b/);
const year = yearMatch ? parseInt(yearMatch[1], 10) : null;
if (year !== null && year <= 2020) {
const infoBox = document.createElement("div");
infoBox.className = "walk-in-info-box";
infoBox.innerHTML = `
<strong>Bemærk:</strong> Vi tilbyder <em>walk-in reparation</em> uden booking,
og de fleste MacBook reparationer kan klares samme dag.
`;
contentDiv.appendChild(infoBox);
}
}
// Problem list
const problemList = document.createElement("div");
problemList.className = "problem-list";
const selectedProblems = new Set();
function updateSummary() {
const summaryBox = document.getElementById("problem-summary");
const selectedProblemsArray = Array.from(selectedProblems);
const summary = selectedProblemsArray
.map((problem) => {
const displayPrice =
problem.original_price == 0
? "Pris ved forespørgsel"
: `${Math.round(problem.original_price)} kr`;
return `- ${problem.name}: ${displayPrice}`;
})
.join("\n");
const totalPrice = selectedProblemsArray.reduce(
(sum, problem) => sum + Math.round(problem.original_price),
0
);
const totalPriceText = totalPrice > 0 ? `\n\nSamlet pris: ${totalPrice} kr` : "";
summaryBox.textContent = summary || "Ingen reparationer valgt endnu.";
if (selectedProblemsArray.length > 0) {
summaryBox.textContent += totalPriceText;
}
}
function updateSubmitButtonState() {
if (selectedProblems.size > 0) {
submitButton.disabled = false;
submitButton.classList.remove("disabled");
} else {
submitButton.disabled = true;
submitButton.classList.add("disabled");
}
}
items.forEach((problem) => {
const problemCard = document.createElement("div");
problemCard.className = "problem-card";
const displayPrice =
problem.original_price == 0
? "Pris ved forespørgsel"
: `${Math.round(problem.original_price)} kr`;
problemCard.innerHTML = `
<div class="problem-info">
<h3 class="problem-name">
${problem.name}
${
problem.description
? `<span class="info-icon" data-tooltip="${problem.description}">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#33658A" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" fill="#ffffff" stroke="#12375A" stroke-width="2"></circle>
<text x="12" y="16" fill="#33658A" font-size="12" font-family="Arial" text-anchor="middle">?</text>
</svg>
</span>`
: ""
}
</h3>
</div>
<div class="problem-action">
<span class="problem-price">${displayPrice}</span>
</div>
`;
const infoIcon = problemCard.querySelector(".info-icon");
if (infoIcon) {
attachTooltipHandler(infoIcon);
}
problemCard.addEventListener("click", () => {
if (problemCard.classList.contains("selected")) {
selectedProblems.delete(problem);
problemCard.classList.remove("selected");
} else {
selectedProblems.add(problem);
problemCard.classList.add("selected");
bookingFormContainer.scrollIntoView({ behavior: "smooth" });
}
bookingDatePicker.set('minDate', calculateMinDate());
updateSummary();
updateSubmitButtonState();
updateColorFieldVisibility();
});
problemList.appendChild(problemCard);
});
contentDiv.appendChild(problemList);
//<textarea id="problem-description" placeholder="Beskriv venligst dit problem"></textarea>
// Booking form container
const bookingFormContainer = document.createElement("div");
bookingFormContainer.className = "booking-form-container";
bookingFormContainer.innerHTML = `
<hr class="separator">
<h3 class="booking-heading">Valgte reparationer</h3>
<form id="booking-form">
<div id="problem-summary">Ingen reparationer valgt endnu</div>
<input type="text" id="customer-name" placeholder="Navn" required />
<input type="email" id="customer-email" placeholder="E-mail" required />
<input type="tel" id="customer-phone" placeholder="Telefonnummer" maxlength="8" required />
<!-- Color Field Wrapper (hidden by default) -->
<div id="device-color-field-wrapper" style="display: none;">
<input type="text" id="device-color" placeholder="Farve på enhed" required />
</div>
<!-- 1) DATE PICKER (FLATPICKR, date-only) -->
<input type="text" id="booking-date" placeholder="Vælg en dato og tid" required />
<!-- 2) TIME SLOTS UI -->
<div class="timeslot-container" id="timeslot-container">
<div class="timeslot-heading" id="timeslot-heading">Vælg et tidspunkt</div>
<div class="timeslot-grid" id="timeslot-grid"></div>
</div>
<input type="text" id="kommentar" placeholder="Evt. kommentar til bestillingen" />
<button type="submit" id="submit-booking">Send Booking</button>
<!-- The new call-us section -->
<div
id="call-us"
style="
border-top: 1px solid #ddd;
padding-top: 15px;
margin-top: 20px;
text-align: center;
font-size: 14px;
line-height: 1.4;
"
>
<strong style="display: block; margin-bottom: 5px;">Har du spørgsmål?</strong>
Ønsker du at snakke med en medarbejder, så giv os et kald:
<a
href="tel:81113337"
style="
display: inline-flex;
align-items: center;
color: #33658A;
background-color: #f9f9f9;
padding: 5px 10px;
border: 1px solid #33658A;
border-radius: 4px;
text-decoration: none;
margin-left: 5px;
"
>
<!-- Phone icon (inline SVG) -->
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="none"
stroke="#33658A"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
style="margin-right: 5px;"
viewBox="0 0 24 24"
>
<path d="M22 16.92V21a1 1 0 01-1.11 1 19.86 19.86 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.86 19.86 0 01-3.07-8.63A1 1 0 013 2h4.09a1 1 0 011 .75c.2.92.52 1.8.96 2.62a1 1 0 01-.27 1.11l-2.2 2.2a16 16 0 006 6l2.2-2.2a1 1 0 011.11-.27 16 16 0 002.62.96 1 1 0 01.75 1V22z"></path>
</svg>
81 11 33 37
</a>
</div>
</form>
`;
contentDiv.appendChild(bookingFormContainer);
// Run once on load and then update periodically (e.g., every minute)
showCallUsIfOpen();
/* ============================================
PART 3: DATE PICKER + TIME SLOT LOGIC
============================================*/
const disabledDates = [
"2024-12-24",
"2024-12-25",
"2024-12-26",
"2024-12-27",
"2024-12-28",
"2024-12-29",
"2024-12-30",
"2024-12-31",
"2025-01-01",
"2025-04-17",
"2025-04-18",
"2025-04-21",
"2025-05-26",
"2025-05-29",
"2025-06-05",
"2025-06-06",
"2025-06-08",
"2025-06-09",
"2025-06-12",
"2025-06-14",
"2025-06-23",
"2025-06-30",
"2025-12-24",
"2025-12-25",
"2025-12-26",
"2025-12-31",
"2025-11-03",
"2025-11-01"
];
// Example: booked time slots in "YYYY-MM-DD HH:mm" format
// These timeslots should appear as "booked" (unavailable) in the UI
const bookedTimeslots = [
"2025-06-25 10:00",
"2025-06-25 10:15",
"2025-06-25 10:30",
"2025-06-25 10:45",
"2025-06-25 11:00",
"2025-06-25 11:15",
"2025-06-25 11:30",
"2025-06-25 11:45",
"2025-06-25 12:00",
"2025-06-27 15:00",
"2025-06-27 15:15",
"2025-06-27 15:30",
"2025-06-27 15:45",
"2025-06-27 16:00",
"2025-06-27 16:15",
"2025-06-27 16:30",
"2025-06-27 16:45",
"2025-06-27 17:00",
"2025-06-27 17:15",
"2025-06-27 17:30",
"2025-06-27 17:45",
"2025-06-27 18:00",
"2025-07-01 15:00",
"2025-07-01 15:15",
"2025-07-01 15:30",
"2025-07-01 15:45",
"2025-07-01 16:00",
"2025-07-01 16:15",
"2025-07-01 16:30",
"2025-07-01 16:45",
"2025-07-01 17:00",
"2025-07-01 17:15",
"2025-07-01 17:30",
"2025-07-01 17:45",
"2025-07-01 18:00",
"2025-10-30 15:00",
"2025-10-30 15:15",
"2025-10-30 15:30",
"2025-10-30 15:45",
"2025-10-30 16:00",
"2025-10-30 16:15",
"2025-10-30 16:30",
"2025-10-30 16:45",
"2025-10-30 17:00",
"2025-10-30 17:15",
"2025-10-30 17:30",
"2025-10-30 17:45",
"2025-10-30 18:00",
"2025-10-31 15:00",
"2025-10-31 15:15",
"2025-10-31 15:30",
"2025-10-31 15:45",
"2025-10-31 16:00",
"2025-10-31 16:15",
"2025-10-31 16:30",
"2025-10-31 16:45",
"2025-10-31 17:00",
"2025-10-31 17:15",
"2025-10-31 17:30",
"2025-10-31 17:45",
"2025-10-31 18:00",
"2025-11-05 14:00",
"2025-11-05 14:15",
"2025-11-05 14:30",
"2025-11-05 14:45",
"2025-11-05 15:00",
"2025-11-05 15:15",
"2025-11-05 15:30",
"2025-11-05 15:45",
"2025-11-05 16:00",
"2025-11-05 16:15",
"2025-11-05 16:30",
"2025-11-05 16:45",
"2025-11-05 17:00",
"2025-11-05 17:15",
"2025-11-05 17:30",
"2025-11-05 17:45",
"2025-11-05 18:00",
];
const deviceName = document.querySelector(".device-info")?.textContent || "Ukendt enhed";
// We'll define standard time slots for weekdays vs. Saturday
const timeSlots = {
weekday: [
"10:00", "10:15", "10:30", "10:45",
"11:00", "11:15", "11:30", "11:45",
"12:00", "12:15", "12:30", "12:45",
"13:00", "13:15", "13:30", "13:45",
"14:00", "14:15", "14:30", "14:45",
"15:00", "15:15", "15:30", "15:45",
"16:00", "16:15", "16:30", "16:45",
"17:00"
],
saturday: [
"10:00", "10:15", "10:30", "10:45",
"11:00", "11:15", "11:30", "11:45",
"12:00", "12:15", "12:30", "12:45",
"13:00", "13:15", "13:30", "13:45",
"14:00", "14:15", "14:30", "14:45",
"15:00"
]
};
function isSameDate(d1, d2) {
return (
d1.getFullYear() === d2.getFullYear() &&
d1.getMonth() === d2.getMonth() &&
d1.getDate() === d2.getDate()
);
}
function formatDateYYYYMMDD(date) {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, "0");
const d = String(date.getDate()).padStart(2, "0");
return `${y}-${m}-${d}`;
}
function addAvailableDays(startDate, daysToAdd, disabledDates) {
let result = new Date(startDate);
while (daysToAdd > 0) {
result.setDate(result.getDate() + 1);
// Skip if weekend
if (result.getDay() === 0 || result.getDay() === 6) continue;
// Skip if disabled date
if (disabledDates.includes(formatDateYYYYMMDD(result))) continue;
// Otherwise, count this day as available
daysToAdd--;
}
return result;
}
function calculateMinDate() {
const now = new Date();
//console.log("Current date:", now);
const isWeekend = now.getDay() === 0 || now.getDay() === 6;
const isDisabledDate = disabledDates.includes(formatDateYYYYMMDD(now));
// Log the current selected problems
//console.log("Selected Problems:", Array.from(selectedProblems));
let availableDaysToAdd = 0;
//console.log("Device name:", deviceName);
// Check if "Skærm (Original Apple)" is selected among the chosen problems.
const hasSkærmOriginal = Array.from(selectedProblems).some(problem => problem.name === "Skærm (Apple Original)");
//console.log("Has 'Skærm (Apple Original)' selected:", hasSkærmOriginal);
// Check if "Diagnose is selected among the chosen problems.
const hasDiagnose = Array.from(selectedProblems).some(problem => problem.name === "Diagnose");
//console.log("Has 'hasDiagnose' selected:", hasDiagnose);
const deviceName = document.querySelector(".device-info")?.textContent.toLowerCase() || "";
const lowerName = deviceName.toLowerCase();
const macbookMatch = deviceName.match(/\b(20\d{2})\b/); // finds year like 2019, 2020, 2021
const macbookYear = macbookMatch ? parseInt(macbookMatch[1], 10) : null;
const isOldMacbook = lowerName.includes("macbook") && macbookYear !== null && macbookYear <= 2020;
const isNewMacbook = lowerName.includes("macbook") && macbookYear !== null && macbookYear > 2020;
// Apply non‑iPhone rules if the device isn’t an iPhone OR if the specific problem is selected.
const problemsArray = Array.from(selectedProblems);
//console.log("problemsArray length:", problemsArray.length);
if (hasDiagnose && problemsArray.length === 1) {
availableDaysToAdd = 0;
} else if (!deviceName.includes("iphone") && isNewMacbook || hasSkærmOriginal) {
if (!isWeekend && !isDisabledDate && now.getHours() < 13 || (now.getHours() === 13 && now.getMinutes() < 30)) {
availableDaysToAdd = 2;
} else {
availableDaysToAdd = 3;
}
}
//console.log("Available days to add:", availableDaysToAdd);
const minDate = addAvailableDays(now, availableDaysToAdd, disabledDates);
//console.log("Calculated minDate:", minDate);
return minDate;
}
function updateColorFieldVisibility() {
const colorFieldWrapper = document.getElementById("device-color-field-wrapper");
const deviceColorInput = document.getElementById("device-color");
if (!colorFieldWrapper) return;
// Collect selected problems that mention "skærm", "bagside", or "topcase"
let problemsWithKeyword = [];
selectedProblems.forEach(problem => {
const pname = problem.name.toLowerCase();
if (pname.includes("skærm") || pname.includes("bagside") || pname.includes("topcase")) {
problemsWithKeyword.push(problem);
}
});
// Default: if there is at least one problem triggering the keywords, require color.
let requireColor = problemsWithKeyword.length > 0;
// Exception: For mobile repairs by Apple, if the only keyword present is "skærm",
// then we do NOT require a color.
const isMobileRepair = selectedCategory && selectedCategory.name.toLowerCase() === "mobil reparation";
const isApple = selectedManufacturer && selectedManufacturer.name.toLowerCase().includes("apple");
if (isMobileRepair && isApple && requireColor) {
// Check if any selected problem includes "bagside" or "topcase"
const hasBagsideOrTopcase = problemsWithKeyword.some(problem => {
const pname = problem.name.toLowerCase();
return pname.includes("bagside") || pname.includes("topcase");
});
// If there are none, then do not require a color.
if (!hasBagsideOrTopcase) {
requireColor = false;
}
}
if (requireColor) {
colorFieldWrapper.style.display = "block";
deviceColorInput.disabled = false;
deviceColorInput.required = true;
} else {
colorFieldWrapper.style.display = "none";
deviceColorInput.disabled = true;
deviceColorInput.required = false;
}
}
/* Flatpickr for date-only */
let bookingDatePicker = flatpickr("#booking-date", {
enableTime: false,
dateFormat: "d/m/Y",
minDate: calculateMinDate(),
/* Disable Sundays & disabledDates array */
disable: [
function(date) {
// Sunday
if (date.getDay() === 0) return true;
// Format date as YYYY-MM-DD
const fmtd = formatDateYYYYMMDD(date);
return disabledDates.includes(fmtd);
}
],
locale: {
firstDayOfWeek: 1,
weekdays: {
shorthand: ["Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør"],
longhand: ["Søndag", "Mandag", "Tirsdag", "Onsdag", "Torsdag", "Fredag", "Lørdag"]
},
months: {
shorthand: ["Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"],
longhand: [
"Januar","Februar","Marts","April","Maj","Juni",
"Juli","August","September","Oktober","November","December"
]
}
},
onReady: function(selectedDates, dateStr, instance) {
// Clear default selection
instance.setDate(null);
// Optionally add a note
const info = instance.calendarContainer.querySelector(".flatpickr-info");
if (!info) {
const note = document.createElement("div");
note.className = "flatpickr-info";
note.innerText = "Booking tilgængelig: Man-Fre: 10:00-17:00. Lørdage: 10:00-15:00. (Søndag lukket)";
instance.calendarContainer.appendChild(note);
}
},
onChange: function(selectedDates) {
if (!selectedDates.length) return;
const chosenDate = selectedDates[0];
// Generate timeslots
generateTimeSlots(chosenDate);
}
});
const timeslotContainer = document.getElementById("timeslot-container");
const timeslotHeading = document.getElementById("timeslot-heading");
const timeslotGrid = document.getElementById("timeslot-grid");
let selectedSlot = null;
function generateTimeSlots(chosenDate) {
timeslotContainer.style.display = "block";
timeslotGrid.innerHTML = "";
selectedSlot = null;
const dayOfWeek = chosenDate.getDay(); // 1=Monday, 6=Saturday
const now = new Date();
const isToday = isSameDate(chosenDate, now);
// Determine if it's Saturday or a weekday
let possibleSlots = [];
if (dayOfWeek === 6) {
possibleSlots = timeSlots.saturday;
} else {
possibleSlots = timeSlots.weekday;
}
// Filter out anything <2 hours from now if it's "today"
const finalSlots = [];
possibleSlots.forEach(time => {
const [hh, mm] = time.split(":").map(Number);
const slotDate = new Date(chosenDate);
slotDate.setHours(hh, mm, 0, 0);
if (isToday) {
// Add 2 hours lead time
const twoHoursFromNow = new Date(now);
twoHoursFromNow.setHours(now.getHours() + 0, now.getMinutes());
if (slotDate < twoHoursFromNow) {
return; // skip
}
}
finalSlots.push(time);
});
// If no finalSlots, show a message
if (!finalSlots.length) {
timeslotHeading.textContent = "Ingen ledige tider på denne dato";
return;
} else {
timeslotHeading.textContent = "Vælg et tidspunkt";
}
// Render the final slots
const dateStr = formatDateYYYYMMDD(chosenDate);
finalSlots.forEach(time => {
const fullDateTime = `${dateStr} ${time}`;
const btn = document.createElement("button");
btn.type = "button";
btn.className = "timeslot-btn";
btn.textContent = time;
// Mark as booked if in bookedTimeslots
if (bookedTimeslots.includes(fullDateTime)) {
btn.classList.add("booked");
btn.disabled = true;
}
btn.addEventListener("click", (e) => {
e.stopPropagation();
if (btn.classList.contains("booked")) return;
// Unselect previous
document.querySelectorAll(".timeslot-btn").forEach(b => b.classList.remove("selected"));
btn.classList.add("selected");
selectedSlot = time;
//console.log("Timeslot selected:", selectedSlot);
});
timeslotGrid.appendChild(btn);
});
}
/* ============================================
PART 4: FORM SUBMISSION
============================================*/
const bookingForm = document.getElementById("booking-form");
const submitButton = document.getElementById("submit-booking");
bookingForm.addEventListener("submit", async (e) => {
e.preventDefault();
const kommentarInput = document.getElementById("kommentar");
const deviceName = document.querySelector(".device-info")?.textContent.toLowerCase() || "";
// Check if device name contains "Enhver" and comment is empty
if (deviceName.includes("enhver") && !kommentarInput.value.trim()) {
alert("Angiv venligst den præcise model i kommentarfeltet for din enhed.");
kommentarInput.focus();
return;
}
// Validate user picked a date & time
const dateInput = document.getElementById("booking-date");
if (!dateInput.value) {
alert("Vælg en dato før du fortsætter.");
dateInput.focus();
return;
}
if (!selectedSlot) {
alert("Vælg et tidspunkt før du fortsætter.");
return;
}
// Because we have date in d/m/Y format, let's parse it or keep as is
// For the final "datetime" field, combine date + selectedSlot
const finalDateTime = `${dateInput.value} ${selectedSlot}`;
submitButton.disabled = true;
submitButton.textContent = "Vent venligst...";
const validProblems = Array.from(selectedProblems).filter((p) => p && p.id);
//console.log("Valid Problems:", validProblems);
const problemNames = validProblems.map((p) => p.name).join("|");
const problemPrices = validProblems
.map((p) => parseFloat(p.original_price).toFixed(0))
.join(", ");
const totalPrice = validProblems.reduce((sum, p) => sum + parseFloat(p.original_price), 0).toFixed(0);
const problemIds = validProblems.map((p) => p.id).join(", ");
const category_id = validProblems.map((p) => p.category_id).join(", ");
const device_id = validProblems.map((p) => p.device).join(", ");
const deviceNameCurrent = document.querySelector(".device-info")?.textContent || "Ukendt enhed";
const formData = new FormData();
formData.append("action", "send_booking_data");
formData.append("name", document.getElementById("customer-name").value);
formData.append("email", document.getElementById("customer-email").value);
formData.append("phone", document.getElementById("customer-phone").value);
// Instead of the old datetime field, we now pass finalDateTime
formData.append("datetime", finalDateTime);
formData.append("device_name", deviceNameCurrent);
formData.append("problem_names", problemNames);
formData.append("problem_prices", problemPrices);
formData.append("total", totalPrice);
formData.append("problem_ids", problemIds);
formData.append("category_id", category_id);
formData.append("device_id", device_id);
formData.append("enhed_farve", document.getElementById("device-color").value);
formData.append("kommentar", document.getElementById("kommentar").value);
formData.append("utm_source", sessionStorage.getItem("utm_source") || "");
formData.append("utm_medium", sessionStorage.getItem("utm_medium") || "");
formData.append("utm_campaign", sessionStorage.getItem("utm_campaign") || "");
formData.append("utm_term", sessionStorage.getItem("utm_term") || "");
formData.append("utm_content", sessionStorage.getItem("utm_content") || "");
formData.append("landing_url", sessionStorage.getItem("td_landing_url") || localStorage.getItem("td_first_landing_url") || window.location.href);
formData.append("landing_referrer", sessionStorage.getItem("td_referrer") || localStorage.getItem("td_first_referrer") || document.referrer || "");
formData.append("booking_page_url", window.location.href);
try {
const response = await fetch("/wp-admin/admin-ajax.php", {
method: "POST",
body: formData,
});
const result = await response.json();
if (result.success) {
window.location.href = "/tak-for-din-booking/";
} else {
alert("Fejl ved afsendelse af booking: Prøv igen eller kontakt os direkte");
submitButton.disabled = false;
submitButton.textContent = "Send Booking";
}
} catch (error) {
console.error("Error submitting booking:", error);
alert("Fejl ved afsendelse af booking: Prøv igen eller kontakt os direkte");
submitButton.disabled = false;
submitButton.textContent = "Send Booking";
}
});
// Initially disable the submit button until a problem is selected
updateSubmitButtonState();
}
}
function generateSchemaMarkup(deviceName, manufacturerName, repairItems) {
const repairServices = repairItems.map(problem => ({
"@type": "Service",
"serviceType": problem.name,
"provider": {
"@type": "LocalBusiness",
"name": "TechDoktoren",
"url": "https://www.techdoktoren.dk",
"address": {
"@type": "PostalAddress",
"addressCountry": "DK",
"addressRegion": "Denmark",
"addressLocality": "København"
}
},
"areaServed": {
"@type": "AdministrativeArea",
"name": "Danmark"
},
"category": `${manufacturerName} ${deviceName} Reparation`,
"serviceOutput": `Repareret ${problem.name}`,
"offers": {
"@type": "Offer",
"price": problem.original_price > 0 ? problem.original_price.toString() : "Pris ved forespørgsel",
"priceCurrency": "DKK"
}
}));
const schemaMarkup = {
"@context": "https://schema.org",
"@type": "ItemList",
"itemListElement": repairServices
};
injectSchemaMarkup(schemaMarkup);
}
function injectSchemaMarkup(schemaMarkup) {
// Convert schema object to JSON-LD format
const schemaJSON = JSON.stringify(schemaMarkup, null, 2);
// Create a new script tag for the schema
const schemaScript = document.createElement("script");
schemaScript.type = "application/ld+json";
schemaScript.innerHTML = schemaJSON;
// Remove any old schema tags to avoid duplication
const oldSchema = document.querySelector('script[type="application/ld+json"]');
if (oldSchema) oldSchema.remove();
// Inject the new schema into the document head
document.head.appendChild(schemaScript);
}
function showCallUsIfOpen() {
const now = new Date();
const day = now.getDay(); // 0 = Sunday, 1 = Monday, …, 6 = Saturday
const hours = now.getHours();
let open = false;
// Monday - Friday: 10:00 - 18:00
if (day >= 1 && day <= 5) {
open = (hours >= 10 && hours < 18);
}
// Saturday: 10:00 - 16:00
else if (day === 6) {
open = (hours >= 10 && hours < 16);
}
// Sunday: closed
else {
open = false;
}
const callUsDiv = document.getElementById("call-us");
if (callUsDiv) {
callUsDiv.style.display = open ? "block" : "none";
}
}
/* Card creation helper */
function createCard(title, imageUrl, onClick) {
const card = document.createElement("div");
card.className = "card";
card.innerHTML = `
<img src="${imageUrl}" alt="${title}" class="card-image">
<h3 class="card-title">${title}</h3>
`;
card.addEventListener("click", onClick);
card.style.pointerEvents = "auto";
return card;
}
/* Initialize the entire widget */
initWidget();
});
</script>