Add massage as a third vertical, driven by a config registry
Introduces /massage alongside barber and beauty: landing page, booking flow, admin support, home tile and SEO entries, in all three languages. Architecture - application/config/verticals.php + vertical_helper.php: one registry entry per vertical (branding, assets, views, behaviour flags). A fourth vertical is a config entry plus content files. - Strangler: barber and beauty keep pointing at their existing view files, so their rendered HTML is unchanged. Only massage uses the new generic pages/vertical-*.php and includes/vertical-*.php views, which collapse the four duplicated per-language nav/footer branches into one. - Pages::vertical() + one route; booking(), booking_finished(), _booking_error() and manage_booking() are now registry-driven. Worker/vertical coupling - getActiveWorkers() derives the vertical from services.service_category_id instead of the workers.is_barber / is_beauty flags, which were a hand-maintained cache of exactly that fact. Verified against production data: the derived set reproduced the stored flags for every worker, in both verticals. No schema change was needed for massage. - The legacy flags are now written through from the category so a rollback cannot strand a new worker, and the admin worker UI shows the derived verticals read-only instead of two dropdowns that controlled nothing. Bug fixes found along the way (all pre-existing) - booking_process() had no server-side category guard; cross-vertical mixing was prevented only by client-side JS. - add-service-form / add-worker-form emitted `selected` on every category option, so the newest category silently became the default. - update-service-form offered only barber/beauty, so editing a service of any other type silently rewrote it. - getWorkerScheduleByDay ignored schedule overrides while getAvailableTimes honoured them, so slots could be shown and then rejected. Added an override-aware getWorkerScheduleForDate() and used it in both guards. - Booking lists dereferenced a null service if one had been hard-deleted. - main.css: .tiles was tuned for exactly two tiles, including an absolutely-positioned .style1 at the 1280px breakpoint. Massage-specific behaviour, opt-in per vertical - strip_category_prefix: grouped service lists show "50 min" under the treatment heading rather than repeating the full name. The full name is carried in data-service-name so the totals panel stays unambiguous, and services.service_name is untouched for emails and admin. - single_service_booking: one treatment per booking, enforced in the UI and in booking_process(). Re-clicking the selection releases it. - Displayed treatment time (50/80/110 min) is in the service name; the booked slot (60/90/120 min) is service_time and covers changing and payment. service_time is never shown to the guest. DB migrations for dev/prod are in documents/ - additive only, no ALTER. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
if($subpage == 'beauty'){
|
||||
include(getcwd().'/application/views/includes/beauty-skeleton-top.php');
|
||||
} else {
|
||||
include(getcwd().'/application/views/includes/barber-skeleton-top.php');
|
||||
// Skeleton comes from the vertical registry so a new vertical needs no
|
||||
// edit here. barber/beauty still resolve to their own skeleton files.
|
||||
$vertical = isset($vertical) ? $vertical : vertical_get($subpage);
|
||||
if(!$vertical){
|
||||
$vertical = vertical_get('barber');
|
||||
}
|
||||
include(getcwd().'/application/views/includes/'.$vertical['skeleton'].'-skeleton-top.php');
|
||||
|
||||
$lang = $selectedLang;
|
||||
|
||||
@@ -341,8 +343,8 @@
|
||||
$isOtherCategory = ($categorySlug !== '' && $serviceItem->serv_cat_slug !== $categorySlug);
|
||||
?>
|
||||
<div class="serviceRow">
|
||||
<div class="serviceCol serviceNameCol" id="serviceName_<?php echo $serviceItem->service_id; ?>">
|
||||
<?php echo htmlspecialchars($serviceItem->service_name); ?>
|
||||
<div class="serviceCol serviceNameCol" id="serviceName_<?php echo $serviceItem->service_id; ?>" data-service-name="<?php echo htmlspecialchars($serviceItem->service_name, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<?php echo htmlspecialchars(vertical_service_label(isset($vertical) ? $vertical : NULL, $serviceItem)); ?>
|
||||
</div>
|
||||
<div class="serviceCol">
|
||||
<input type="time" class="serviceTime" id="time_<?php echo $serviceItem->service_id; ?>" value="<?php echo $serviceItem->service_time; ?>"/>
|
||||
@@ -462,6 +464,19 @@
|
||||
});
|
||||
|
||||
$('.service').click(function(){
|
||||
<?php if ( ! empty($vertical['single_service_booking'])): ?>
|
||||
/* See booking-form-*.php: one treatment at a time for this vertical. */
|
||||
if($(this).is(':checked')){
|
||||
$('.service').not(this)
|
||||
.prop('checked', false)
|
||||
.prop('disabled', true)
|
||||
.closest('.serviceRow').addClass('serviceRowDisabled');
|
||||
}
|
||||
else{
|
||||
$('.service').prop('disabled', false);
|
||||
$('.serviceRow').removeClass('serviceRowDisabled');
|
||||
}
|
||||
<?php endif; ?>
|
||||
recalcServices();
|
||||
if($('.service').is(':checked')){
|
||||
var selectedClasses = $(this).attr('class');
|
||||
@@ -500,7 +515,9 @@
|
||||
var selected_id = $(this).attr('id');
|
||||
var priceEl = document.getElementById('servicePrice_'+selected_id).innerHTML.trim();
|
||||
totalPrice += parseInt(priceEl);
|
||||
var nameEl = document.getElementById('serviceName_'+selected_id).innerHTML.trim();
|
||||
var nameNode = document.getElementById('serviceName_'+selected_id);
|
||||
// Prefer the full name; the visible label may be abbreviated.
|
||||
var nameEl = (nameNode.getAttribute('data-service-name') || nameNode.innerHTML).trim();
|
||||
selectedServices += '<div class="totalServiceRow"><div class="selectedServiceCol totalSelectedServiceName">'+nameEl+'</div><div class="selectedServiceCol totalSelectedServicePrice">'+priceEl+' kr</div></div>';
|
||||
|
||||
var currentTotal = $('#servicelength').val();
|
||||
@@ -632,9 +649,5 @@
|
||||
</script>
|
||||
|
||||
<?php
|
||||
if($subpage == 'beauty'){
|
||||
include(getcwd().'/application/views/includes/beauty-skeleton-bottom.php');
|
||||
} else {
|
||||
include(getcwd().'/application/views/includes/barber-skeleton-bottom.php');
|
||||
}
|
||||
include(getcwd().'/application/views/includes/'.$vertical['skeleton'].'-skeleton-bottom.php');
|
||||
?>
|
||||
|
||||
Reference in New Issue
Block a user