Files
studiobeve.no/application/views/pages/manage-booking.php
T
UbuntuandClaude Opus 4.6 0e172ceea8 Sync outstanding changes: beauty forms, config updates, gitignore
- Update beauty booking forms (en, hu, no)
- Update manage-booking view, worker calendar view
- Update config.php, google_calendar.php, GoogleCalendar library
- Add CLAUDE.md project instructions
- Add service-account-key.json to .gitignore
- Update ledigestilling page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-07 13:02:18 +00:00

638 lines
28 KiB
PHP
Executable File

<?php
if($subpage == 'beauty'){
include(getcwd().'/application/views/includes/beauty-skeleton-top.php');
} else {
include(getcwd().'/application/views/includes/barber-skeleton-top.php');
}
$lang = $selectedLang;
$labels = array(
'no' => array(
'title' => 'Administrer bestilling',
'current' => 'Din nåværende bestilling',
'date' => 'Dato',
'time' => 'Tid',
'worker' => 'Arbeider',
'services' => 'Tjenester',
'total' => 'Totalpris',
'cancel_btn' => 'Avbestill bestillingen',
'cancel_confirm' => 'Er du sikker på at du vil avbestille bestillingen?',
'modify_title' => 'Endre bestilling',
'step1' => 'TRINN 1',
'step2' => 'TRINN 2',
'step3' => 'TRINN 3',
'services_label' => 'Tjenester',
'choose_worker' => 'Velg en arbeider',
'next' => 'Neste',
'back' => 'Tilbake',
'send' => 'Bekreft endring',
'no_times' => 'Ingen ledige tider denne dagen!',
'expired_msg' => 'Denne bestillingen er allerede passert og kan ikke endres.',
'cutoff_msg' => 'Bestillingen er innen 24 timer og kan ikke endres eller avbestilles.',
),
'hu' => array(
'title' => 'Foglalás kezelése',
'current' => 'Jelenlegi foglalásod',
'date' => 'Dátum',
'time' => 'Időpont',
'worker' => 'Dolgozó',
'services' => 'Szolgáltatás(ok)',
'total' => 'Összesen',
'cancel_btn' => 'Foglalás törlése',
'cancel_confirm' => 'Biztosan törölni szeretnéd a foglalást?',
'modify_title' => 'Foglalás módosítása',
'step1' => '1. LÉPÉS',
'step2' => '2. LÉPÉS',
'step3' => '3. LÉPÉS',
'services_label' => 'Szolgáltatások',
'choose_worker' => 'Válassz dolgozót',
'next' => 'Tovább',
'back' => 'Vissza',
'send' => 'Módosítás megerősítése',
'no_times' => 'Ezen a napon nincs szabad időpont!',
'expired_msg' => 'Ez a foglalás már elmúlt, nem módosítható.',
'cutoff_msg' => 'A foglalás 24 órán belül van, nem módosítható vagy törölhető.',
),
'en' => array(
'title' => 'Manage Booking',
'current' => 'Your current booking',
'date' => 'Date',
'time' => 'Time',
'worker' => 'Worker',
'services' => 'Services',
'total' => 'Total price',
'cancel_btn' => 'Cancel booking',
'cancel_confirm' => 'Are you sure you want to cancel your booking?',
'modify_title' => 'Modify booking',
'step1' => 'STEP 1',
'step2' => 'STEP 2',
'step3' => 'STEP 3',
'services_label' => 'Services',
'choose_worker' => 'Choose a worker',
'next' => 'Next',
'back' => 'Back',
'send' => 'Confirm changes',
'no_times' => 'There is no available time on this day!',
'expired_msg' => 'This booking has already passed and cannot be modified.',
'cutoff_msg' => 'Your booking is within 24 hours and can no longer be changed or cancelled.',
),
);
$l = isset($labels[$lang]) ? $labels[$lang] : $labels['en'];
?>
<style>
/* ── Manage-booking overrides ───────────────────────────────────────────── */
/* Navigation buttons: use normal flow instead of position:absolute */
#manageForm .bookingButtonContainer {
position: static;
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 10px 10px;
height: auto;
}
#manageForm .bookingButton,
#manageForm .leftButton,
#manageForm .rightButton {
position: static;
}
/* Worker table: allow natural height for pre-rendered workers */
#manageForm .workerTable {
height: auto;
margin-bottom: 20px;
width: 100%;
max-width: 500px;
}
/* Step 3: overflow:auto creates a BFC that contains floated children */
#manageForm #bookingStep3 {
overflow: auto;
}
/* Summary table */
.manage-summary-table {
width: 100%;
max-width: 540px;
margin: 0 auto;
border-collapse: collapse;
}
.manage-summary-table td {
padding: 8px 12px;
border: 1px solid #e0c5bb;
vertical-align: top;
}
.manage-summary-table td:first-child {
font-weight: bold;
white-space: nowrap;
width: 130px;
background: #fdf5f2;
}
.manage-summary-box {
background: #fdf5f2;
border-radius: 6px;
padding: 20px;
margin: 15px auto;
max-width: 580px;
text-align: left;
}
.manage-summary-title {
text-align: center;
font-size: 1.1em;
color: #a5705b;
margin-bottom: 14px;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.manage-cancel-bar {
text-align: center;
margin: 20px auto;
max-width: 580px;
}
.manage-cancel-btn {
background: #7a3a2a;
color: #fff;
border: none;
padding: 11px 28px;
border-radius: 4px;
cursor: pointer;
font-size: 0.85em;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.manage-cancel-btn:hover {
background: #5e2a1a;
}
.manage-divider {
border: none;
border-top: 1px solid #d9b8ac;
margin: 30px auto;
max-width: 580px;
}
.manage-modify-title {
font-size: 1em;
text-align: center;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #a5705b;
margin-bottom: 20px;
}
/* ─────────────────────────────────────────────────────────────────────── */
</style>
<div class="bookingContainer">
<div class="bookingTitle"><?php echo $l['title']; ?></div>
<?php if($status == 'expired'): ?>
<p style="color:#c0392b; text-align:center; margin-top:30px; padding:20px;"><?php echo $l['expired_msg']; ?></p>
<?php elseif($status == 'cutoff'): ?>
<p style="color:#c0392b; text-align:center; margin-top:30px; padding:20px;"><?php echo $l['cutoff_msg']; ?></p>
<?php else: ?>
<!-- Current booking summary -->
<div class="manage-summary-box">
<div class="manage-summary-title"><?php echo $l['current']; ?></div>
<table class="manage-summary-table">
<tr>
<td><?php echo $l['date']; ?></td>
<td><?php echo $booking->booking_date; ?></td>
</tr>
<tr>
<td><?php echo $l['time']; ?></td>
<td><?php echo date('H:i', strtotime($booking->booking_start_time)); ?> &ndash; <?php echo date('H:i', strtotime($booking->booking_finish_time)); ?></td>
</tr>
<tr>
<td><?php echo $l['worker']; ?></td>
<td><?php echo htmlspecialchars($worker->worker_name); ?></td>
</tr>
<tr>
<td><?php echo $l['services']; ?></td>
<td>
<?php
$totalPrice = 0;
$serviceIds = unserialize($booking->service_ids);
if(is_array($serviceIds)){
echo '<ul style="margin:0; padding-left:16px;">';
foreach($serviceIds as $sid){
$svc = $this->Service_model->getServiceById($sid);
if($svc){
$name = ($lang == 'hu') ? $svc->service_name_hu : (($lang == 'no') ? $svc->service_name_no : $svc->service_name_en);
$totalPrice += $svc->service_price;
echo '<li>'.htmlspecialchars($name).' &mdash; '.$svc->service_price.' kr</li>';
}
}
echo '</ul>';
}
?>
</td>
</tr>
<tr>
<td><?php echo $l['total']; ?></td>
<td><strong><?php echo $totalPrice; ?> kr</strong></td>
</tr>
</table>
</div>
<!-- Cancel -->
<div class="manage-cancel-bar">
<form method="post" action="<?php echo SITEURL; ?>manage-booking-cancel"
onsubmit="return confirm('<?php echo addslashes($l['cancel_confirm']); ?>');">
<input type="hidden" name="token" value="<?php echo htmlspecialchars($token); ?>">
<input type="hidden" name="lang" value="<?php echo htmlspecialchars($lang); ?>">
<button type="submit" class="manage-cancel-btn"><?php echo $l['cancel_btn']; ?></button>
</form>
</div>
<hr class="manage-divider">
<!-- Modify section -->
<div class="manage-modify-title"><?php echo $l['modify_title']; ?></div>
<ul class="steps-indicator steps-3">
<li>
<div class="stepTitleContainer">
<div class="stepLine"></div>
<div class="stepCircle activeStep" id="stepCircle1"></div>
<div class="stepTitle"><?php echo $l['step1']; ?></div>
</div>
</li>
<li>
<div class="stepTitleContainer">
<div class="stepLine"></div>
<div class="stepCircle" id="stepCircle2"></div>
<div class="stepTitle"><?php echo $l['step2']; ?></div>
</div>
</li>
<li>
<div class="stepTitleContainer">
<div class="stepCircle" id="stepCircle3"></div>
<div class="stepTitle"><?php echo $l['step3']; ?></div>
</div>
</li>
</ul>
<div class="totalPanelContainer">
<div id="totalPanel" style="display:none;">
<div class="serviceBookingDateContainer">
<div id="serviceBookingDate"></div>
<div id="serviceBookingTime"></div>
</div>
<div class="serviceBookingWorkerContainer"></div>
<div class="serviceContent"></div>
<div class="totalSelectedTime"></div>
<div class="totalPrice"></div>
</div>
<div id="totalPanelBtnContainer">
<div id="totalPanelBtn" style="display:none;"><i class="fas fa-caret-right"></i></div>
</div>
</div>
<form method="post" action="<?php echo SITEURL; ?>manage-booking-process" id="manageForm">
<input type="hidden" name="token" value="<?php echo htmlspecialchars($token); ?>">
<input type="hidden" name="lang" value="<?php echo htmlspecialchars($lang); ?>">
<input type="hidden" name="subpage" value="<?php echo htmlspecialchars($subpage); ?>">
<input type="hidden" name="sendBooking" value="1">
<!-- Step 1: Services -->
<div class="BookingStepContainer" id="bookingStep1">
<div class="totalTime"><input type="time" id="servicelength" name="servicelength" value="00:00"></div>
<div class="bookingTable">
<div class="panel panel-primary">
<div class="panel-heading"><?php echo $l['services_label']; ?></div>
</div>
</div>
<div class="serviceTable">
<div class="serviceHeader">
<div class="serviceCol serviceNameCol">Service name</div>
<div class="serviceCol"></div>
<div class="serviceCol servicePriceCol">Price</div>
<div class="serviceCol serviceInfoCol"></div>
<div class="serviceCol serviceCheckCol"></div>
</div>
<?php
function manage_canon_cat($s) {
$s = html_entity_decode((string)$s, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$s = strip_tags($s);
return trim($s);
}
if(is_array($services) && count($services) > 0){
$groups = array();
$order = array();
foreach($services as $serviceItem){
$label = manage_canon_cat($serviceItem->service_category ?? '');
if($label === '') $label = 'Other';
if(!isset($groups[$label])){ $groups[$label] = array(); $order[] = $label; }
$groups[$label][] = $serviceItem;
}
foreach($order as $label){
?>
<div class="serviceRow">
<div class="serviceCol" style="width:100%"><h4><?php echo htmlspecialchars($label); ?></h4></div>
</div>
<?php
foreach($groups[$label] as $serviceItem){
$isChecked = in_array($serviceItem->service_id, $selectedServiceIds);
?>
<div class="serviceRow">
<div class="serviceCol serviceNameCol" id="serviceName_<?php echo $serviceItem->service_id; ?>">
<?php echo htmlspecialchars($serviceItem->service_name); ?>
</div>
<div class="serviceCol">
<input type="time" class="serviceTime" id="time_<?php echo $serviceItem->service_id; ?>" value="<?php echo $serviceItem->service_time; ?>"/>
</div>
<div class="serviceCol servicePriceCol" id="servicePrice_<?php echo $serviceItem->service_id; ?>">
<?php echo $serviceItem->service_price; ?>
</div>
<div class="serviceCol serviceInfoCol">
<div class="serviceInfoIcon">
<i class="fas fa-info-circle"></i>
<div class="serviceInfoLabel"><?php echo htmlspecialchars($serviceItem->service_description); ?></div>
</div>
</div>
<div class="serviceCol serviceCheckCol">
<input type="hidden" name="service_<?php echo $serviceItem->service_id; ?>" value="0"/>
<input type="checkbox"
class="service <?php echo $serviceItem->serv_cat_slug; ?>"
id="<?php echo $serviceItem->service_id; ?>"
name="service_<?php echo $serviceItem->service_id; ?>"
value="1"
<?php echo $isChecked ? 'checked' : ''; ?>/>
</div>
</div>
<?php
}
}
}
?>
</div>
<div class="bookingButtonContainer">
<span></span>
<input type="button" class="rightButton bookingButton" onclick="goToStep(2);" value="<?php echo $l['next']; ?>"/>
</div>
</div>
<!-- Step 2: Worker -->
<div class="BookingStepContainer" id="bookingStep2" style="display:none;">
<input type="hidden" name="worker_id" id="worker_id" value="<?php echo $booking->worker_id; ?>">
<div class="bookingHeader"><?php echo $l['choose_worker']; ?></div>
<div class="workerTable" id="workerTable">
<?php
if(is_array($workers)){
foreach($workers as $workerItem){
$isSelected = ($workerItem->worker_id == $booking->worker_id);
?>
<label for="worker_<?php echo $workerItem->worker_id; ?>">
<div class="profileRow">
<div class="profileCell profileRadioBtn">
<input type="radio" name="worker_radio" class="workers"
id="worker_<?php echo $workerItem->worker_id; ?>"
onclick="setWorker(<?php echo $workerItem->worker_id; ?>)"
<?php echo $isSelected ? 'checked' : ''; ?>>
</div>
<div class="profileCell profileImage">
<img src="<?php echo SITEURL; ?>assets/img/<?php echo $workerItem->worker_profile_img; ?>" style="width:65px;height:65px;border-radius:50%;object-fit:cover;">
</div>
<div class="profileCell">
<div class="profileName"><?php echo htmlspecialchars($workerItem->worker_name); ?></div>
<div class="profileDescription"><?php echo htmlspecialchars($workerItem->worker_info); ?></div>
</div>
</div>
</label>
<?php
}
}
?>
</div>
<div class="bookingButtonContainer">
<input type="button" class="leftButton bookingButton" onclick="goToStep(1);" value="<?php echo $l['back']; ?>"/>
<input type="button" class="rightButton bookingButton" onclick="goToStep(3);" value="<?php echo $l['next']; ?>"/>
</div>
</div>
<!-- Step 3: Date & time -->
<div class="BookingStepContainer" id="bookingStep3" style="display:none;">
<div class="bookingCalendar">
<div id="datepicker"></div>
</div>
<div class="bookingResultsWrapper">
<div class="availableTimesContainerTitle"></div>
<div class="availableTimesContainer" style="padding:5px;">
<input type="hidden" name="booking_date" id="booking_date" value="<?php echo date('Y-m-d'); ?>">
<input type="hidden" name="booking_start_time" id="booking_time" value="">
<div class="availableTimes"></div>
</div>
</div>
<div style="clear:both;"></div>
<div class="bookingButtonContainer">
<input type="button" class="leftButton bookingButton" onclick="goToStep(2);" value="<?php echo $l['back']; ?>"/>
<input type="submit" class="rightButton bookingButton" id="sendManageBtn" name="sendBooking" value="<?php echo $l['send']; ?>" disabled/>
</div>
</div>
</form>
<?php endif; ?>
</div>
<script>
var noTimesMsg = '<?php echo addslashes($l['no_times']); ?>';
$(document).ready(function(){
recalcServices();
$('#totalPanelBtn').click(function(){
if($('#totalPanel').is(':visible')){
$('#totalPanelBtn').html('<i class="fas fa-caret-left"></i>');
$('#totalPanel').hide();
$('#totalPanelBtn').css('border-top-left-radius','5px');
$('#totalPanelBtn').css('border-bottom-left-radius','5px');
} else {
$('#totalPanelBtn').html('<i class="fas fa-caret-right"></i>');
$('#totalPanel').show();
$('#totalPanelBtn').css('border-top-left-radius','0px');
$('#totalPanelBtn').css('border-bottom-left-radius','0px');
}
});
$('.service').click(function(){
recalcServices();
if($('.service').is(':checked')){
var selectedClasses = $(this).attr('class');
var selectedclassesArray = selectedClasses.split(' ');
$('.service').each(function(){
var actualElementArray = $(this).attr('class').split(' ');
if(selectedclassesArray[1] !== actualElementArray[1]){
$(this).attr('disabled', true);
}
});
getAvailableWorkersByServiceCategorySlug(selectedclassesArray[1]);
} else {
$('.service').attr('disabled', false);
}
});
$('#booking_time').change(function(){
if($(this).val() !== ''){
$('#sendManageBtn').prop('disabled', false);
} else {
$('#sendManageBtn').prop('disabled', true);
}
});
});
function recalcServices(){
var totalPrice = 0;
var classIsChecked = 0;
$('#servicelength').val('00:00');
var selectedServices = '<div class="selectedServiceTable">';
selectedServices += '<div class="totalServiceRow serviceHeader"><div class="selectedServiceCol totalSelectedServiceName">Service name</div><div class="selectedServiceCol totalSelectedServicePrice">Price</div></div>';
$('.service').each(function(){
if($(this).is(':checked')){
classIsChecked = 1;
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();
selectedServices += '<div class="totalServiceRow"><div class="selectedServiceCol totalSelectedServiceName">'+nameEl+'</div><div class="selectedServiceCol totalSelectedServicePrice">'+priceEl+' kr</div></div>';
var currentTotal = $('#servicelength').val();
var svcDT = new Date('<?php echo date('Y-m-d'); ?> ' + $('#time_'+selected_id).val());
var subH = moment.utc(currentTotal,'HH:mm').add(svcDT.getHours(),'hour').format('HH:mm');
var subM = moment.utc(subH,'HH:mm').add(svcDT.getMinutes(),'minutes').format('HH:mm');
$('#servicelength').val(subM);
}
});
selectedServices += '</div>';
$('.serviceContent').html(selectedServices);
$('.totalPrice').html('<span class="totalTitle">Total:</span> <span>'+totalPrice+'</span><span class="totalTitle"> kr</span>');
$('.totalSelectedTime').html('<span class="totalTitle">Total time:</span> <span>'+$('#servicelength').val()+'</span>');
if(classIsChecked){
$('#totalPanelBtn').show();
$('#totalPanel').show();
} else {
$('#totalPanelBtn').hide();
$('#totalPanel').hide();
}
}
function getAvailableWorkersByServiceCategorySlug(categorySlug){
$.ajax({
url: '<?php echo base_url(); ?>ajax',
type: 'POST',
data: { action: 'getAvailableWorkersByServiceCategory', serv_cat_slug: categorySlug },
dataType: 'json',
}).done(function(result){
$('#workerTable').html(result.workerListShow);
if(result.workers && result.workers.length > 0){
setWorker(result.workers[0].worker_id);
}
});
}
$(function(){
$('#datepicker').datepicker({
firstDay: 1,
defaultDate: new Date(),
dateFormat: 'yy-mm-dd',
onSelect: function(dateText){
$('#booking_date').val(this.value);
$('#sendManageBtn').prop('disabled', true);
$('#booking_time').val('');
getAvailableTimesOfTheDay(this.value);
}
});
});
function getAvailableTimesOfTheDay(selectedDate){
$('.bookingResultsWrapper').show();
$('#serviceBookingDate').html(selectedDate).show();
$.ajax({
url: '<?php echo base_url(); ?>ajax',
type: 'POST',
data: {
action: 'getAvailableTimes',
worker_id: $('#worker_id').val(),
servicelength: $('#servicelength').val(),
selectedDate: selectedDate
},
}).done(function(result){
if(result != 0){
$('.availableTimes').html(result);
} else {
$('.availableTimes').html(noTimesMsg);
}
});
}
function setSelectedTime(time, id){
$('#booking_time').val(time).trigger('change');
$('#serviceBookingTime').html(time).show();
$('.availableBookingTime').removeClass('selectedTime');
$('#bookingTime_'+id).addClass('selectedTime');
}
function goToStep(stepNumber){
if(canGotoNextStep(stepNumber)){
$('.stepCircle').removeClass('activeStep');
$('.BookingStepContainer').hide();
$('#bookingStep'+stepNumber).show();
if(stepNumber >= 3){
$('#stepCircle1, #stepCircle2, #stepCircle3').addClass('activeStep');
if(stepNumber == 3 && $('#datepicker').val() !== ''){
getAvailableTimesOfTheDay($('#datepicker').val());
}
$('.bookingSummaryContainer').show();
} else if(stepNumber == 2){
$('#stepCircle1, #stepCircle2').addClass('activeStep');
$('.bookingResultsWrapper').hide();
$('.bookingSummaryContainer').hide();
} else {
$('#stepCircle1').addClass('activeStep');
$('.bookingResultsWrapper').hide();
$('.bookingSummaryContainer').hide();
}
$('html').scrollTop(0);
} else {
alert('<?php echo ($lang == "no") ? "Vennligst fyll ut de påkrevde feltene!" : (($lang == "hu") ? "Kérjük töltsd ki a kötelező mezőket!" : "Please fill in the required fields!"); ?>');
}
}
function canGotoNextStep(stepNumber){
if(stepNumber == 1 || stepNumber == 4) return 1;
if(stepNumber == 2){
var checked = 0;
$('.service').each(function(){ if($(this).is(':checked')) checked = 1; });
return checked;
}
if(stepNumber == 3){
var checked = 0;
$('.workers').each(function(){ if($(this).is(':checked')) checked = 1; });
return checked;
}
return 1;
}
function setWorker(worker_id){
$('#worker_id').val(worker_id);
$('.serviceBookingWorkerContainer').hide().html('');
}
</script>
<?php
if($subpage == 'beauty'){
include(getcwd().'/application/views/includes/beauty-skeleton-bottom.php');
} else {
include(getcwd().'/application/views/includes/barber-skeleton-bottom.php');
}
?>