Adds Online Booking configuration and preview components
Introduces comprehensive online booking feature with: - Localization support for booking settings - ViewComponents for booking URL, settings, company info, hours, and preview - Responsive preview with device toggle functionality - Integrated with settings page and translation files Enhances application's online booking configuration interface
This commit is contained in:
parent
eba6bd646d
commit
435d9f11b7
17 changed files with 891 additions and 25 deletions
|
|
@ -5,7 +5,9 @@
|
||||||
"Bash(npm run build:*)",
|
"Bash(npm run build:*)",
|
||||||
"Bash(node -e:*)",
|
"Bash(node -e:*)",
|
||||||
"Bash(dir /s /b \"C:\\\\Users\\\\Janus Knudsen\\\\source\\\\swp-repos\\\\PlanTempus\")",
|
"Bash(dir /s /b \"C:\\\\Users\\\\Janus Knudsen\\\\source\\\\swp-repos\\\\PlanTempus\")",
|
||||||
"Bash(findstr:*)"
|
"Bash(findstr:*)",
|
||||||
|
"Bash(dir \"C:\\\\Users\\\\Janus Knudsen\\\\source\\\\swp-repos\\\\PlanTempus\\\\PlanTempus.Application\\\\wwwroot\\\\*.html\")",
|
||||||
|
"Bash(dir /s /b \"C:\\\\Users\\\\Janus Knudsen\\\\source\\\\swp-repos\\\\PlanTempus\\\\PlanTempus.Application\\\\Features\\\\OnlineBooking\\\\*.cs\" \"C:\\\\Users\\\\Janus Knudsen\\\\source\\\\swp-repos\\\\PlanTempus\\\\PlanTempus.Application\\\\Features\\\\OnlineBooking\\\\*.cshtml\")"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -707,5 +707,60 @@
|
||||||
"noData": "Ingen økonomiske data tilgængelige"
|
"noData": "Ingen økonomiske data tilgængelige"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"onlineBooking": {
|
||||||
|
"title": "Online Booking",
|
||||||
|
"subtitle": "Konfigurer og preview din booking-side",
|
||||||
|
"status": {
|
||||||
|
"active": "Aktiv",
|
||||||
|
"inactive": "Inaktiv"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"title": "Booking URL",
|
||||||
|
"copy": "Kopiér",
|
||||||
|
"open": "Åbn i ny fane"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "Booking-indstillinger",
|
||||||
|
"enableBooking": "Aktivér online booking",
|
||||||
|
"enableBookingDesc": "Tillad kunder at booke tider online",
|
||||||
|
"bookAhead": "Book frem i tiden",
|
||||||
|
"minNotice": "Minimum varsel",
|
||||||
|
"cancelDeadline": "Aflysningsfrist"
|
||||||
|
},
|
||||||
|
"company": {
|
||||||
|
"title": "Virksomhedsoplysninger",
|
||||||
|
"edit": "Rediger",
|
||||||
|
"name": "Navn",
|
||||||
|
"address": "Adresse",
|
||||||
|
"zipCity": "Postnr + By",
|
||||||
|
"phone": "Telefon",
|
||||||
|
"email": "Email"
|
||||||
|
},
|
||||||
|
"hours": {
|
||||||
|
"title": "Åbningstider",
|
||||||
|
"edit": "Rediger",
|
||||||
|
"closed": "Lukket"
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"title": "Preview",
|
||||||
|
"desktop": "Desktop",
|
||||||
|
"tablet": "Tablet",
|
||||||
|
"mobile": "Mobil"
|
||||||
|
},
|
||||||
|
"allSettings": "Alle indstillinger"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "Indstillinger",
|
||||||
|
"tabs": {
|
||||||
|
"company": "Virksomhed",
|
||||||
|
"calendar": "Kalender",
|
||||||
|
"booking": "Online Booking",
|
||||||
|
"billing": "Faktura & Kvittering",
|
||||||
|
"reminders": "Påmindelser",
|
||||||
|
"payments": "Betalinger",
|
||||||
|
"modules": "Moduler",
|
||||||
|
"tracking": "Tracking"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<swp-card>
|
||||||
|
<swp-card-header>
|
||||||
|
<swp-card-title>
|
||||||
|
<i class="ph ph-buildings"></i>
|
||||||
|
<span localize="onlineBooking.company.title">Virksomhedsoplysninger</span>
|
||||||
|
</swp-card-title>
|
||||||
|
<swp-section-action href="/indstillinger" localize="onlineBooking.company.edit">Rediger</swp-section-action>
|
||||||
|
</swp-card-header>
|
||||||
|
<swp-card-content>
|
||||||
|
<swp-edit-section>
|
||||||
|
<swp-edit-row>
|
||||||
|
<swp-edit-label localize="onlineBooking.company.name">Navn</swp-edit-label>
|
||||||
|
<swp-edit-value>Salon Beauty</swp-edit-value>
|
||||||
|
</swp-edit-row>
|
||||||
|
<swp-edit-row>
|
||||||
|
<swp-edit-label localize="onlineBooking.company.address">Adresse</swp-edit-label>
|
||||||
|
<swp-edit-value>Hovedgaden 123</swp-edit-value>
|
||||||
|
</swp-edit-row>
|
||||||
|
<swp-edit-row>
|
||||||
|
<swp-edit-label localize="onlineBooking.company.zipCity">Postnr + By</swp-edit-label>
|
||||||
|
<swp-edit-value>2100 København Ø</swp-edit-value>
|
||||||
|
</swp-edit-row>
|
||||||
|
<swp-edit-row>
|
||||||
|
<swp-edit-label localize="onlineBooking.company.phone">Telefon</swp-edit-label>
|
||||||
|
<swp-edit-value>+45 12 34 56 78</swp-edit-value>
|
||||||
|
</swp-edit-row>
|
||||||
|
<swp-edit-row>
|
||||||
|
<swp-edit-label localize="onlineBooking.company.email">Email</swp-edit-label>
|
||||||
|
<swp-edit-value>kontakt@salonbeauty.dk</swp-edit-value>
|
||||||
|
</swp-edit-row>
|
||||||
|
</swp-edit-section>
|
||||||
|
</swp-card-content>
|
||||||
|
</swp-card>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.OnlineBooking.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewComponent for displaying company information used in booking.
|
||||||
|
/// </summary>
|
||||||
|
public class OnlineBookingCompanyViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
<swp-card>
|
||||||
|
<swp-card-header>
|
||||||
|
<swp-card-title>
|
||||||
|
<i class="ph ph-clock"></i>
|
||||||
|
<span localize="onlineBooking.hours.title">Åbningstider</span>
|
||||||
|
</swp-card-title>
|
||||||
|
<swp-section-action href="/indstillinger" localize="onlineBooking.hours.edit">Rediger</swp-section-action>
|
||||||
|
</swp-card-header>
|
||||||
|
<swp-card-content>
|
||||||
|
<swp-hours-table>
|
||||||
|
<swp-hours-row>
|
||||||
|
<swp-hours-day localize="employees.detail.hours.monday">Mandag</swp-hours-day>
|
||||||
|
<swp-toggle-slider data-value="yes">
|
||||||
|
<swp-toggle-option>Ja</swp-toggle-option>
|
||||||
|
<swp-toggle-option>Nej</swp-toggle-option>
|
||||||
|
</swp-toggle-slider>
|
||||||
|
<swp-hours-time>
|
||||||
|
<input type="time" value="09:00">
|
||||||
|
<span>-</span>
|
||||||
|
<input type="time" value="17:00">
|
||||||
|
</swp-hours-time>
|
||||||
|
</swp-hours-row>
|
||||||
|
<swp-hours-row>
|
||||||
|
<swp-hours-day localize="employees.detail.hours.tuesday">Tirsdag</swp-hours-day>
|
||||||
|
<swp-toggle-slider data-value="yes">
|
||||||
|
<swp-toggle-option>Ja</swp-toggle-option>
|
||||||
|
<swp-toggle-option>Nej</swp-toggle-option>
|
||||||
|
</swp-toggle-slider>
|
||||||
|
<swp-hours-time>
|
||||||
|
<input type="time" value="09:00">
|
||||||
|
<span>-</span>
|
||||||
|
<input type="time" value="17:00">
|
||||||
|
</swp-hours-time>
|
||||||
|
</swp-hours-row>
|
||||||
|
<swp-hours-row>
|
||||||
|
<swp-hours-day localize="employees.detail.hours.wednesday">Onsdag</swp-hours-day>
|
||||||
|
<swp-toggle-slider data-value="yes">
|
||||||
|
<swp-toggle-option>Ja</swp-toggle-option>
|
||||||
|
<swp-toggle-option>Nej</swp-toggle-option>
|
||||||
|
</swp-toggle-slider>
|
||||||
|
<swp-hours-time>
|
||||||
|
<input type="time" value="09:00">
|
||||||
|
<span>-</span>
|
||||||
|
<input type="time" value="17:00">
|
||||||
|
</swp-hours-time>
|
||||||
|
</swp-hours-row>
|
||||||
|
<swp-hours-row>
|
||||||
|
<swp-hours-day localize="employees.detail.hours.thursday">Torsdag</swp-hours-day>
|
||||||
|
<swp-toggle-slider data-value="yes">
|
||||||
|
<swp-toggle-option>Ja</swp-toggle-option>
|
||||||
|
<swp-toggle-option>Nej</swp-toggle-option>
|
||||||
|
</swp-toggle-slider>
|
||||||
|
<swp-hours-time>
|
||||||
|
<input type="time" value="09:00">
|
||||||
|
<span>-</span>
|
||||||
|
<input type="time" value="19:00">
|
||||||
|
</swp-hours-time>
|
||||||
|
</swp-hours-row>
|
||||||
|
<swp-hours-row>
|
||||||
|
<swp-hours-day localize="employees.detail.hours.friday">Fredag</swp-hours-day>
|
||||||
|
<swp-toggle-slider data-value="yes">
|
||||||
|
<swp-toggle-option>Ja</swp-toggle-option>
|
||||||
|
<swp-toggle-option>Nej</swp-toggle-option>
|
||||||
|
</swp-toggle-slider>
|
||||||
|
<swp-hours-time>
|
||||||
|
<input type="time" value="09:00">
|
||||||
|
<span>-</span>
|
||||||
|
<input type="time" value="17:00">
|
||||||
|
</swp-hours-time>
|
||||||
|
</swp-hours-row>
|
||||||
|
<swp-hours-row>
|
||||||
|
<swp-hours-day localize="employees.detail.hours.saturday">Lørdag</swp-hours-day>
|
||||||
|
<swp-toggle-slider data-value="yes">
|
||||||
|
<swp-toggle-option>Ja</swp-toggle-option>
|
||||||
|
<swp-toggle-option>Nej</swp-toggle-option>
|
||||||
|
</swp-toggle-slider>
|
||||||
|
<swp-hours-time>
|
||||||
|
<input type="time" value="10:00">
|
||||||
|
<span>-</span>
|
||||||
|
<input type="time" value="14:00">
|
||||||
|
</swp-hours-time>
|
||||||
|
</swp-hours-row>
|
||||||
|
<swp-hours-row>
|
||||||
|
<swp-hours-day localize="employees.detail.hours.sunday">Søndag</swp-hours-day>
|
||||||
|
<swp-toggle-slider data-value="no">
|
||||||
|
<swp-toggle-option>Ja</swp-toggle-option>
|
||||||
|
<swp-toggle-option>Nej</swp-toggle-option>
|
||||||
|
</swp-toggle-slider>
|
||||||
|
<swp-hours-closed localize="onlineBooking.hours.closed">Lukket</swp-hours-closed>
|
||||||
|
</swp-hours-row>
|
||||||
|
</swp-hours-table>
|
||||||
|
</swp-card-content>
|
||||||
|
</swp-card>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.OnlineBooking.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewComponent for displaying opening hours used in booking.
|
||||||
|
/// </summary>
|
||||||
|
public class OnlineBookingHoursViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
<swp-online-booking-preview-container>
|
||||||
|
<swp-preview-header>
|
||||||
|
<swp-preview-title>
|
||||||
|
<i class="ph ph-eye"></i>
|
||||||
|
<span localize="onlineBooking.preview.title">Preview</span>
|
||||||
|
</swp-preview-title>
|
||||||
|
<swp-device-toggle>
|
||||||
|
<swp-device-btn class="active" data-device="desktop" title="Desktop">
|
||||||
|
<i class="ph ph-desktop"></i>
|
||||||
|
</swp-device-btn>
|
||||||
|
<swp-device-btn data-device="tablet" title="Tablet">
|
||||||
|
<i class="ph ph-device-tablet"></i>
|
||||||
|
</swp-device-btn>
|
||||||
|
<swp-device-btn data-device="mobile" title="Mobil">
|
||||||
|
<i class="ph ph-device-mobile"></i>
|
||||||
|
</swp-device-btn>
|
||||||
|
</swp-device-toggle>
|
||||||
|
</swp-preview-header>
|
||||||
|
|
||||||
|
<swp-preview-frame data-device="desktop">
|
||||||
|
<swp-preview-content>
|
||||||
|
<!-- Placeholder booking UI -->
|
||||||
|
<swp-booking-demo>
|
||||||
|
<swp-booking-demo-header>
|
||||||
|
<swp-booking-demo-logo>SB</swp-booking-demo-logo>
|
||||||
|
<swp-booking-demo-info>
|
||||||
|
<h2>Salon Beauty</h2>
|
||||||
|
<p>Hovedgaden 123, 2100 København Ø</p>
|
||||||
|
</swp-booking-demo-info>
|
||||||
|
</swp-booking-demo-header>
|
||||||
|
|
||||||
|
<swp-booking-demo-section>
|
||||||
|
<h3>Vælg service</h3>
|
||||||
|
<swp-booking-demo-services>
|
||||||
|
<swp-booking-demo-service class="selected">
|
||||||
|
<span class="name">Dame klip</span>
|
||||||
|
<span class="meta">45 min · 450 kr</span>
|
||||||
|
</swp-booking-demo-service>
|
||||||
|
<swp-booking-demo-service>
|
||||||
|
<span class="name">Herre klip</span>
|
||||||
|
<span class="meta">30 min · 350 kr</span>
|
||||||
|
</swp-booking-demo-service>
|
||||||
|
<swp-booking-demo-service>
|
||||||
|
<span class="name">Farvning</span>
|
||||||
|
<span class="meta">90 min · 850 kr</span>
|
||||||
|
</swp-booking-demo-service>
|
||||||
|
<swp-booking-demo-service>
|
||||||
|
<span class="name">Balayage</span>
|
||||||
|
<span class="meta">120 min · 1.450 kr</span>
|
||||||
|
</swp-booking-demo-service>
|
||||||
|
</swp-booking-demo-services>
|
||||||
|
</swp-booking-demo-section>
|
||||||
|
|
||||||
|
<swp-booking-demo-section>
|
||||||
|
<h3>Vælg medarbejder</h3>
|
||||||
|
<swp-booking-demo-employees>
|
||||||
|
<swp-booking-demo-employee class="selected">
|
||||||
|
<swp-avatar class="size-sm">MJ</swp-avatar>
|
||||||
|
<span>Maria Jensen</span>
|
||||||
|
</swp-booking-demo-employee>
|
||||||
|
<swp-booking-demo-employee>
|
||||||
|
<swp-avatar class="size-sm purple">AH</swp-avatar>
|
||||||
|
<span>Anna Hansen</span>
|
||||||
|
</swp-booking-demo-employee>
|
||||||
|
<swp-booking-demo-employee>
|
||||||
|
<swp-avatar class="size-sm blue">LP</swp-avatar>
|
||||||
|
<span>Louise Petersen</span>
|
||||||
|
</swp-booking-demo-employee>
|
||||||
|
</swp-booking-demo-employees>
|
||||||
|
</swp-booking-demo-section>
|
||||||
|
|
||||||
|
<swp-booking-demo-section>
|
||||||
|
<h3>Vælg dato og tid</h3>
|
||||||
|
<swp-booking-demo-calendar>
|
||||||
|
<swp-booking-demo-month>Januar 2026</swp-booking-demo-month>
|
||||||
|
<swp-booking-demo-days>
|
||||||
|
<span class="day-header">Ma</span>
|
||||||
|
<span class="day-header">Ti</span>
|
||||||
|
<span class="day-header">On</span>
|
||||||
|
<span class="day-header">To</span>
|
||||||
|
<span class="day-header">Fr</span>
|
||||||
|
<span class="day-header">Lø</span>
|
||||||
|
<span class="day-header">Sø</span>
|
||||||
|
<span class="day other">27</span>
|
||||||
|
<span class="day other">28</span>
|
||||||
|
<span class="day other">29</span>
|
||||||
|
<span class="day other">30</span>
|
||||||
|
<span class="day other">31</span>
|
||||||
|
<span class="day">1</span>
|
||||||
|
<span class="day">2</span>
|
||||||
|
<span class="day">3</span>
|
||||||
|
<span class="day">4</span>
|
||||||
|
<span class="day">5</span>
|
||||||
|
<span class="day">6</span>
|
||||||
|
<span class="day">7</span>
|
||||||
|
<span class="day">8</span>
|
||||||
|
<span class="day">9</span>
|
||||||
|
<span class="day">10</span>
|
||||||
|
<span class="day">11</span>
|
||||||
|
<span class="day">12</span>
|
||||||
|
<span class="day">13</span>
|
||||||
|
<span class="day selected">14</span>
|
||||||
|
<span class="day">15</span>
|
||||||
|
<span class="day">16</span>
|
||||||
|
</swp-booking-demo-days>
|
||||||
|
</swp-booking-demo-calendar>
|
||||||
|
</swp-booking-demo-section>
|
||||||
|
|
||||||
|
<swp-booking-demo-footer>
|
||||||
|
<swp-btn class="primary full-width">Book tid</swp-btn>
|
||||||
|
</swp-booking-demo-footer>
|
||||||
|
</swp-booking-demo>
|
||||||
|
</swp-preview-content>
|
||||||
|
</swp-preview-frame>
|
||||||
|
</swp-online-booking-preview-container>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
const deviceBtns = document.querySelectorAll('swp-device-btn');
|
||||||
|
const previewFrame = document.querySelector('swp-preview-frame');
|
||||||
|
|
||||||
|
deviceBtns.forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
deviceBtns.forEach(b => b.classList.remove('active'));
|
||||||
|
btn.classList.add('active');
|
||||||
|
previewFrame.setAttribute('data-device', btn.getAttribute('data-device'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.OnlineBooking.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewComponent for the booking preview iframe with device toggle.
|
||||||
|
/// </summary>
|
||||||
|
public class OnlineBookingPreviewViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
<swp-card>
|
||||||
|
<swp-card-header>
|
||||||
|
<swp-card-title>
|
||||||
|
<i class="ph ph-sliders"></i>
|
||||||
|
<span localize="onlineBooking.settings.title">Booking-indstillinger</span>
|
||||||
|
</swp-card-title>
|
||||||
|
</swp-card-header>
|
||||||
|
<swp-card-content>
|
||||||
|
<swp-toggle-row>
|
||||||
|
<swp-toggle-info>
|
||||||
|
<swp-toggle-label localize="onlineBooking.settings.enableBooking">Aktivér online booking</swp-toggle-label>
|
||||||
|
<swp-toggle-desc localize="onlineBooking.settings.enableBookingDesc">Tillad kunder at booke tider online</swp-toggle-desc>
|
||||||
|
</swp-toggle-info>
|
||||||
|
<swp-toggle-slider data-value="yes">
|
||||||
|
<swp-toggle-option>Ja</swp-toggle-option>
|
||||||
|
<swp-toggle-option>Nej</swp-toggle-option>
|
||||||
|
</swp-toggle-slider>
|
||||||
|
</swp-toggle-row>
|
||||||
|
|
||||||
|
<swp-section-divider></swp-section-divider>
|
||||||
|
|
||||||
|
<swp-edit-section>
|
||||||
|
<swp-edit-row class="wide-label">
|
||||||
|
<swp-edit-label localize="onlineBooking.settings.bookAhead">Book frem i tiden</swp-edit-label>
|
||||||
|
<swp-edit-select>
|
||||||
|
<select id="booking-ahead">
|
||||||
|
<option value="7">7 dage</option>
|
||||||
|
<option value="14">14 dage</option>
|
||||||
|
<option value="30" selected>30 dage</option>
|
||||||
|
<option value="60">60 dage</option>
|
||||||
|
<option value="90">90 dage</option>
|
||||||
|
</select>
|
||||||
|
</swp-edit-select>
|
||||||
|
</swp-edit-row>
|
||||||
|
<swp-edit-row class="wide-label">
|
||||||
|
<swp-edit-label localize="onlineBooking.settings.minNotice">Minimum varsel</swp-edit-label>
|
||||||
|
<swp-edit-select>
|
||||||
|
<select id="min-notice">
|
||||||
|
<option value="0">Ingen begrænsning</option>
|
||||||
|
<option value="1">1 time</option>
|
||||||
|
<option value="2" selected>2 timer</option>
|
||||||
|
<option value="4">4 timer</option>
|
||||||
|
<option value="24">24 timer</option>
|
||||||
|
</select>
|
||||||
|
</swp-edit-select>
|
||||||
|
</swp-edit-row>
|
||||||
|
<swp-edit-row class="wide-label">
|
||||||
|
<swp-edit-label localize="onlineBooking.settings.cancelDeadline">Aflysningsfrist</swp-edit-label>
|
||||||
|
<swp-edit-select>
|
||||||
|
<select id="cancel-deadline">
|
||||||
|
<option value="0">Ingen frist</option>
|
||||||
|
<option value="2">2 timer før</option>
|
||||||
|
<option value="4">4 timer før</option>
|
||||||
|
<option value="24" selected>24 timer før</option>
|
||||||
|
<option value="48">48 timer før</option>
|
||||||
|
</select>
|
||||||
|
</swp-edit-select>
|
||||||
|
</swp-edit-row>
|
||||||
|
</swp-edit-section>
|
||||||
|
</swp-card-content>
|
||||||
|
</swp-card>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.OnlineBooking.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewComponent for online booking settings (toggle, booking ahead, notice, cancellation).
|
||||||
|
/// </summary>
|
||||||
|
public class OnlineBookingSettingsViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<swp-card>
|
||||||
|
<swp-card-header>
|
||||||
|
<swp-card-title>
|
||||||
|
<i class="ph ph-link"></i>
|
||||||
|
<span localize="onlineBooking.url.title">Booking URL</span>
|
||||||
|
</swp-card-title>
|
||||||
|
</swp-card-header>
|
||||||
|
<swp-card-content>
|
||||||
|
<swp-url-field>
|
||||||
|
<input type="text" value="https://book.plantempus.dk/salonbeauty" readonly id="booking-url">
|
||||||
|
<swp-url-copy title="Kopier link" onclick="copyBookingUrl()">
|
||||||
|
<i class="ph ph-copy"></i>
|
||||||
|
</swp-url-copy>
|
||||||
|
</swp-url-field>
|
||||||
|
<swp-url-actions>
|
||||||
|
<swp-btn class="secondary sm" onclick="window.open(document.getElementById('booking-url').value, '_blank')">
|
||||||
|
<i class="ph ph-arrow-square-out"></i>
|
||||||
|
<span localize="onlineBooking.url.open">Åbn i ny fane</span>
|
||||||
|
</swp-btn>
|
||||||
|
</swp-url-actions>
|
||||||
|
</swp-card-content>
|
||||||
|
</swp-card>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function copyBookingUrl() {
|
||||||
|
const input = document.getElementById('booking-url');
|
||||||
|
navigator.clipboard.writeText(input.value).then(() => {
|
||||||
|
// Could show a toast notification here
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.OnlineBooking.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewComponent for displaying the booking URL with copy and open actions.
|
||||||
|
/// </summary>
|
||||||
|
public class OnlineBookingUrlViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
@page "/online-booking"
|
||||||
|
@model PlanTempus.Application.Features.OnlineBooking.Pages.IndexModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Online Booking";
|
||||||
|
}
|
||||||
|
|
||||||
|
<swp-sticky-header>
|
||||||
|
<swp-header-content>
|
||||||
|
<swp-page-header>
|
||||||
|
<swp-page-title>
|
||||||
|
<h1 localize="onlineBooking.title">Online Booking</h1>
|
||||||
|
<p localize="onlineBooking.subtitle">Konfigurer og preview din booking-side</p>
|
||||||
|
</swp-page-title>
|
||||||
|
<swp-page-actions>
|
||||||
|
<swp-status-indicator data-active="true">
|
||||||
|
<i class="ph ph-check-circle icon"></i>
|
||||||
|
<span localize="onlineBooking.status.active">Aktiv</span>
|
||||||
|
</swp-status-indicator>
|
||||||
|
</swp-page-actions>
|
||||||
|
</swp-page-header>
|
||||||
|
</swp-header-content>
|
||||||
|
</swp-sticky-header>
|
||||||
|
|
||||||
|
<swp-page-container>
|
||||||
|
<swp-online-booking-layout>
|
||||||
|
<!-- Column 1: Settings -->
|
||||||
|
<swp-online-booking-settings>
|
||||||
|
@await Component.InvokeAsync("OnlineBookingUrl")
|
||||||
|
@await Component.InvokeAsync("OnlineBookingSettings")
|
||||||
|
@await Component.InvokeAsync("OnlineBookingCompany")
|
||||||
|
@await Component.InvokeAsync("OnlineBookingHours")
|
||||||
|
|
||||||
|
<swp-all-settings-link href="/indstillinger">
|
||||||
|
<span localize="onlineBooking.allSettings">Alle indstillinger</span>
|
||||||
|
<i class="ph ph-arrow-right"></i>
|
||||||
|
</swp-all-settings-link>
|
||||||
|
</swp-online-booking-settings>
|
||||||
|
|
||||||
|
<!-- Column 2: Preview -->
|
||||||
|
@await Component.InvokeAsync("OnlineBookingPreview")
|
||||||
|
</swp-online-booking-layout>
|
||||||
|
</swp-page-container>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.OnlineBooking.Pages;
|
||||||
|
|
||||||
|
public class IndexModel : PageModel
|
||||||
|
{
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,18 +18,10 @@
|
||||||
|
|
||||||
<!-- Tab Bar -->
|
<!-- Tab Bar -->
|
||||||
<swp-tab-bar>
|
<swp-tab-bar>
|
||||||
<swp-tab class="active" data-tab="general">
|
<swp-tab class="active" data-tab="calendar">
|
||||||
<i class="ph ph-buildings"></i>
|
|
||||||
<span localize="settings.tabs.company">Virksomhed</span>
|
|
||||||
</swp-tab>
|
|
||||||
<swp-tab data-tab="calendar">
|
|
||||||
<i class="ph ph-calendar"></i>
|
<i class="ph ph-calendar"></i>
|
||||||
<span localize="settings.tabs.calendar">Kalender</span>
|
<span localize="settings.tabs.calendar">Kalender</span>
|
||||||
</swp-tab>
|
</swp-tab>
|
||||||
<swp-tab data-tab="booking">
|
|
||||||
<i class="ph ph-globe"></i>
|
|
||||||
<span localize="settings.tabs.booking">Online Booking</span>
|
|
||||||
</swp-tab>
|
|
||||||
<swp-tab data-tab="billing">
|
<swp-tab data-tab="billing">
|
||||||
<i class="ph ph-receipt"></i>
|
<i class="ph ph-receipt"></i>
|
||||||
<span localize="settings.tabs.billing">Faktura & Kvittering</span>
|
<span localize="settings.tabs.billing">Faktura & Kvittering</span>
|
||||||
|
|
@ -53,27 +45,13 @@
|
||||||
</swp-tab-bar>
|
</swp-tab-bar>
|
||||||
</swp-sticky-header>
|
</swp-sticky-header>
|
||||||
|
|
||||||
<!-- Tab Content: Virksomhed -->
|
|
||||||
<swp-tab-content data-tab="general" class="active">
|
|
||||||
<swp-page-container>
|
|
||||||
@await Component.InvokeAsync("SettingsGeneral")
|
|
||||||
</swp-page-container>
|
|
||||||
</swp-tab-content>
|
|
||||||
|
|
||||||
<!-- Tab Content: Kalender -->
|
<!-- Tab Content: Kalender -->
|
||||||
<swp-tab-content data-tab="calendar">
|
<swp-tab-content data-tab="calendar" class="active">
|
||||||
<swp-page-container>
|
<swp-page-container>
|
||||||
@await Component.InvokeAsync("SettingsCalendar")
|
@await Component.InvokeAsync("SettingsCalendar")
|
||||||
</swp-page-container>
|
</swp-page-container>
|
||||||
</swp-tab-content>
|
</swp-tab-content>
|
||||||
|
|
||||||
<!-- Tab Content: Online Booking -->
|
|
||||||
<swp-tab-content data-tab="booking">
|
|
||||||
<swp-page-container>
|
|
||||||
@await Component.InvokeAsync("SettingsBooking")
|
|
||||||
</swp-page-container>
|
|
||||||
</swp-tab-content>
|
|
||||||
|
|
||||||
<!-- Tab Content: Faktura & Kvittering -->
|
<!-- Tab Content: Faktura & Kvittering -->
|
||||||
<swp-tab-content data-tab="billing">
|
<swp-tab-content data-tab="billing">
|
||||||
<swp-page-container>
|
<swp-page-container>
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
<link rel="stylesheet" href="~/css/customers.css">
|
<link rel="stylesheet" href="~/css/customers.css">
|
||||||
<link rel="stylesheet" href="~/css/suppliers.css">
|
<link rel="stylesheet" href="~/css/suppliers.css">
|
||||||
<link rel="stylesheet" href="~/css/settings.css">
|
<link rel="stylesheet" href="~/css/settings.css">
|
||||||
|
<link rel="stylesheet" href="~/css/online-booking.css">
|
||||||
<link rel="stylesheet" href="~/css/reports.css">
|
<link rel="stylesheet" href="~/css/reports.css">
|
||||||
@await RenderSectionAsync("Styles", required: false)
|
@await RenderSectionAsync("Styles", required: false)
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
360
PlanTempus.Application/wwwroot/css/online-booking.css
Normal file
360
PlanTempus.Application/wwwroot/css/online-booking.css
Normal file
|
|
@ -0,0 +1,360 @@
|
||||||
|
/**
|
||||||
|
* Online Booking - Preview and Settings page
|
||||||
|
*
|
||||||
|
* Feature-specific styling only.
|
||||||
|
* Reuses: swp-card, swp-card-header, swp-card-title, swp-edit-section,
|
||||||
|
* swp-toggle-row, swp-url-field, swp-hours-table (components.css, settings.css)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
PAGE LAYOUT (Two-column)
|
||||||
|
=========================================== */
|
||||||
|
swp-online-booking-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: var(--card-gap);
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
swp-online-booking-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-online-booking-preview-container {
|
||||||
|
order: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
SETTINGS COLUMN
|
||||||
|
=========================================== */
|
||||||
|
swp-online-booking-settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--card-gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* URL Actions (below URL field) */
|
||||||
|
swp-url-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-3);
|
||||||
|
margin-top: var(--spacing-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All Settings Link */
|
||||||
|
swp-all-settings-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--spacing-2);
|
||||||
|
padding: var(--spacing-4);
|
||||||
|
color: var(--color-teal);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity var(--transition-fast);
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
& i {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
PREVIEW COLUMN
|
||||||
|
=========================================== */
|
||||||
|
swp-online-booking-preview-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: sticky;
|
||||||
|
top: calc(var(--sticky-header-height, 120px) + var(--section-gap));
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-preview-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--spacing-4) var(--spacing-5);
|
||||||
|
background: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-bottom: none;
|
||||||
|
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-preview-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-3);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--color-text);
|
||||||
|
|
||||||
|
& i {
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--color-teal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
DEVICE TOGGLE
|
||||||
|
=========================================== */
|
||||||
|
swp-device-toggle {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-1);
|
||||||
|
padding: var(--spacing-1);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-device-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
|
||||||
|
& i {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-background);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: var(--color-teal);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
PREVIEW FRAME
|
||||||
|
=========================================== */
|
||||||
|
swp-preview-frame {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--spacing-6);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0 0 var(--radius-lg) var(--radius-lg);
|
||||||
|
min-height: 600px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-preview-content {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Device variants */
|
||||||
|
swp-preview-frame[data-device="desktop"] swp-preview-content {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-preview-frame[data-device="tablet"] swp-preview-content {
|
||||||
|
max-width: 768px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-preview-frame[data-device="mobile"] swp-preview-content {
|
||||||
|
max-width: 375px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
BOOKING DEMO (Placeholder UI)
|
||||||
|
=========================================== */
|
||||||
|
swp-booking-demo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-booking-demo-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-4);
|
||||||
|
padding: var(--spacing-6);
|
||||||
|
background: linear-gradient(135deg, var(--color-teal), #00796b);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-booking-demo-logo {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-booking-demo-info {
|
||||||
|
& h2 {
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
margin: 0 0 var(--spacing-1) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& p {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
opacity: 0.9;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-booking-demo-section {
|
||||||
|
padding: var(--spacing-5);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
|
||||||
|
& h3 {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin: 0 0 var(--spacing-4) 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Services list */
|
||||||
|
swp-booking-demo-services {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-booking-demo-service {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--spacing-3) var(--spacing-4);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-background-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background: var(--bg-teal-strong);
|
||||||
|
border: 1px solid var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .name {
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .meta {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Employees list */
|
||||||
|
swp-booking-demo-employees {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-3);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-booking-demo-employee {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-2);
|
||||||
|
padding: var(--spacing-2) var(--spacing-4) var(--spacing-2) var(--spacing-2);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-background-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background: var(--bg-teal-strong);
|
||||||
|
border: 1px solid var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
& span {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calendar */
|
||||||
|
swp-booking-demo-calendar {
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: var(--spacing-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-booking-demo-month {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
color: var(--color-text);
|
||||||
|
margin-bottom: var(--spacing-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-booking-demo-days {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
gap: var(--spacing-1);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
& .day-header {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
padding: var(--spacing-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .day {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
padding: var(--spacing-2);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-background-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background: var(--color-teal);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.other {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-booking-demo-footer {
|
||||||
|
padding: var(--spacing-5);
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue