diff --git a/application/config/routes.php b/application/config/routes.php
index 2c7e7a2..443dcab 100755
--- a/application/config/routes.php
+++ b/application/config/routes.php
@@ -94,30 +94,6 @@ $route['bookings'] = 'admin/bookings';
$route['bookings/add-booking'] = 'admin/add_booking';
$route['bookings/update-booking/(:any)'] = 'admin/update_booking/$1';
$route['bookings/booking-process'] = 'admin/booking_process';
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+$route['(:any)/manage-booking/(:any)'] = 'pages/manage_booking/$1/$2';
+$route['manage-booking-process'] = 'pages/manage_booking_process';
+$route['manage-booking-cancel'] = 'pages/manage_booking_cancel';
diff --git a/application/controllers/Pages.php b/application/controllers/Pages.php
index 126a900..1bdbf1b 100755
--- a/application/controllers/Pages.php
+++ b/application/controllers/Pages.php
@@ -324,6 +324,7 @@ class Pages extends CI_Controller {
$servicelength = gmdate('H:i:s', $totalLengthSeconds);
// 3. Build booking array
+ $manageToken = bin2hex(random_bytes(16));
$bookingArray = array(
'guest_name' => $_POST['guest_name'],
'guest_email' => $_POST['guest_email'],
@@ -335,6 +336,7 @@ class Pages extends CI_Controller {
'service_ids' => serialize($selectedServiceArray),
'guest_confirmed' => '0',
'guest_confirm_code' => '1234',
+ 'manage_token' => $manageToken,
);
$bookingDate = $bookingArray['booking_date'];
@@ -459,6 +461,7 @@ class Pages extends CI_Controller {
$content .= '';
$content .= '
| Totalpris: | '.$totalServicePrice.' kr |
';
$content .= '';
+ $content .= '
Administrer bestilling
';
$content .= '
Dersom noe skulle dukke opp og du ikke kan komme til avtalen din, vennligst gi oss beskjed minst 24 timer i forveien, slik at vi kan gi timen videre til en annen kunde og våre kollegers tid ikke blir stående ubrukt; avbestillinger innen 24 timer, eller manglende oppmøte, vil bli belastet med full pris for behandlingen; avbestilling kan gjøres via e-post til info@studiobeve.no eller ved å sende oss en melding på Instagram eller Facebook. Takk for forståelsen og samarbeidet!';
$content .= '
';
$content .= '
STUDIOBEVE';
@@ -483,6 +486,7 @@ class Pages extends CI_Controller {
$content .= '';
$content .= '| Total price: | '.$totalServicePrice.' kr |
';
$content .= '';
+ $content .= '
Manage booking
';
$content .= '
If something comes up and you can’t make it to your booked appointment, please let us know at least 24 hours in advance so we can offer the time slot to another guest and our colleagues’ time won’t remain unused; cancellations within 24 hours or no-shows will be charged the full service fee; you can cancel by emailing info@studiobeve.no, or by sending us a message on Instagram or Facebook. Thank you for your understanding and cooperation!';
$content .= '
';
$content .= '
STUDIOBEVE';
@@ -507,6 +511,7 @@ class Pages extends CI_Controller {
$content .= '';
$content .= '| Összesen fizetendő: | '.$totalServicePrice.' kr |
';
$content .= '';
+ $content .= '
Foglalás kezelése
';
$content .= '
Kérünk, ha valami közbejön, és mégsem tudsz eljönni a lefoglalt időpontodra, jelezd nekünk legalább 24 órával előre, így más vendégnek is tudunk szabad időpontot biztosítani, és kollégáink ideje sem marad kihasználatlanul; 24 órán belüli lemondás, illetve meg nem jelenés esetén a szolgáltatás teljes díja felszámításra kerül; a lemondást az info@studiobeve.no
e-mail címen, vagy Instagram- és Facebook-üzenetben tudod megtenni. Köszönjük a megértést és az együttműködést!';
$content .= '
';
@@ -666,38 +671,284 @@ class Pages extends CI_Controller {
}
}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ public function manage_booking($lang, $token){
+ $this->load->helper('url');
+ $this->load->model('Service_model');
+
+ $booking = $this->Service_model->getBookingByToken($token);
+ if(!$booking){
+ show_404();
+ return;
+ }
+
+ // Determine booking type from services
+ $serviceIds = unserialize($booking->service_ids);
+ $subpage = 'barber';
+ if(is_array($serviceIds) && !empty($serviceIds)){
+ $firstService = $this->Service_model->getServiceById($serviceIds[0]);
+ if($firstService){
+ $subpage = $firstService->service_type;
+ }
+ }
+
+ $now = new DateTime('now', new DateTimeZone('Europe/Oslo'));
+ $appointmentDT = new DateTime($booking->booking_date.' '.$booking->booking_start_time, new DateTimeZone('Europe/Oslo'));
+
+ $data['token'] = $token;
+ $data['booking'] = $booking;
+ $data['selectedLang'] = $lang;
+ $data['subpage'] = $subpage;
+ $data['pageTitle'] = '';
+ $data['status'] = 'ok';
+
+ if($appointmentDT < $now){
+ $data['status'] = 'expired';
+ } elseif(($appointmentDT->getTimestamp() - $now->getTimestamp()) < 86400){
+ $data['status'] = 'cutoff';
+ }
+
+ $data['services'] = $this->Service_model->getAllServiceByServiceType($subpage, $lang);
+ $data['workers'] = $this->Service_model->getActiveWorkers($subpage);
+ $data['selectedServiceIds'] = is_array($serviceIds) ? $serviceIds : array();
+ $data['worker'] = $this->Service_model->getWorkerById($booking->worker_id);
+
+ $this->load->view('pages/manage-booking', $data);
+ }
+
+ public function manage_booking_process(){
+ $this->load->helper('url');
+ $this->load->model('Service_model');
+
+ if(!isset($_POST['token']) || !isset($_POST['sendBooking'])){
+ header('Location:'.SITEURL);
+ return;
+ }
+
+ $token = $_POST['token'];
+ $booking = $this->Service_model->getBookingByToken($token);
+ if(!$booking){
+ show_404();
+ return;
+ }
+
+ $now = new DateTime('now', new DateTimeZone('Europe/Oslo'));
+ $appointmentDT = new DateTime($booking->booking_date.' '.$booking->booking_start_time, new DateTimeZone('Europe/Oslo'));
+ if(($appointmentDT->getTimestamp() - $now->getTimestamp()) < 86400){
+ $this->output->set_status_header(403)->set_content_type('application/json')
+ ->set_output(json_encode(['error' => 'Modification cutoff has passed.']));
+ return;
+ }
+
+ // Build selected service array and total length
+ $selectedServiceArray = array();
+ $totalLengthSeconds = 0;
+ foreach($_POST as $key => $value){
+ if(strpos($key, 'service_') === 0 && $value == '1'){
+ $serviceId = str_replace('service_', '', $key);
+ $selectedServiceArray[] = $serviceId;
+ $service = $this->Service_model->getServiceById($serviceId);
+ if($service){
+ $parts = explode(':', $service->service_time);
+ $totalLengthSeconds += ($parts[0] * 3600) + ($parts[1] * 60);
+ }
+ }
+ }
+ $servicelength = gmdate('H:i:s', $totalLengthSeconds);
+
+ $newBookingDate = $_POST['booking_date'];
+ $newStartTime = $_POST['booking_start_time'];
+ $newWorkerId = $_POST['worker_id'];
+ $lang = $_POST['lang'];
+ $subpage = $_POST['subpage'];
+
+ // Schedule check
+ $weekday = date('w', strtotime($newBookingDate));
+ $schedule = $this->Service_model->getWorkerScheduleByDay($newWorkerId, $weekday);
+ if(!$schedule){
+ $this->output->set_status_header(403)->set_content_type('application/json')
+ ->set_output(json_encode(['error' => 'Worker is not available on this day.']));
+ return;
+ }
+ if($newStartTime < $schedule->start_time || $newStartTime >= $schedule->end_time){
+ $this->output->set_status_header(403)->set_content_type('application/json')
+ ->set_output(json_encode(['error' => 'Booking time is outside worker schedule.']));
+ return;
+ }
+ if(!$this->Service_model->isWorkerAvailableThisWeek($newWorkerId, $newBookingDate)){
+ $this->output->set_status_header(403)->set_content_type('application/json')
+ ->set_output(json_encode(['error' => 'Worker only works every second week.']));
+ return;
+ }
+
+ // Slot availability (temporarily exclude current booking)
+ $this->Service_model->updateBooking($booking->booking_id, ['booking_date' => '1970-01-01']);
+ $available = $this->Service_model->getAvailableTimes($newWorkerId, $newBookingDate, $servicelength);
+ $this->Service_model->updateBooking($booking->booking_id, ['booking_date' => $booking->booking_date]);
+
+ if(!is_array($available) || !in_array($newStartTime, $available)){
+ $this->output->set_status_header(409)->set_content_type('application/json')
+ ->set_output(json_encode(['error' => 'Conflict. Timeslot is taken or does not fit the service.']));
+ return;
+ }
+
+ // 3-month limit
+ $today = new DateTime();
+ $maxDate = (clone $today)->modify('+3 months');
+ $selectedDate = new DateTime($newBookingDate);
+ if($selectedDate > $maxDate){
+ $this->output->set_status_header(403)->set_content_type('application/json')
+ ->set_output(json_encode(['error' => 'Bookings can only be made up to 3 months in advance.']));
+ return;
+ }
+
+ // Calculate finish time
+ $bookingStart = new DateTime($newStartTime);
+ $bookingLength = new DateTime($servicelength);
+ $bookingEnd = clone $bookingStart;
+ $bookingEnd->add(new DateInterval('PT'.intval($bookingLength->format('H')).'H'));
+ $bookingEnd->add(new DateInterval('PT'.intval($bookingLength->format('i')).'M'));
+ $closingTime = new DateTime('18:00');
+ if($bookingEnd > $closingTime){
+ $this->output->set_status_header(403)->set_content_type('application/json')
+ ->set_output(json_encode(['error' => 'Selected time exceeds business hours.']));
+ return;
+ }
+ $finishTime = $bookingEnd->format('H:i:s');
+
+ // Update booking
+ $this->Service_model->updateBooking($booking->booking_id, array(
+ 'worker_id' => $newWorkerId,
+ 'booking_date' => $newBookingDate,
+ 'booking_start_time' => $newStartTime,
+ 'booking_finish_time' => $finishTime,
+ 'service_ids' => serialize($selectedServiceArray),
+ ));
+
+ $worker = $this->Service_model->getWorkerById($newWorkerId);
+ $serviceArray = array();
+ foreach($selectedServiceArray as $serviceItem){
+ $sel = $this->Service_model->getServiceById($serviceItem);
+ if(is_object($sel)) $serviceArray[] = $sel;
+ }
+
+ $manageLink = SITEURL.$lang.'/manage-booking/'.$token;
+ $totalServicePrice = 0;
+ foreach($serviceArray as $s){ $totalServicePrice += $s->service_price; }
+
+ switch($lang){
+ case 'no':
+ $subject = '[studiobeve] Din bestilling er oppdatert';
+ $content = 'Kjære gjest,
Din bestilling er oppdatert.
';
+ $content .= '';
+ $content .= '| Arbeider: | '.$worker->worker_name.' |
';
+ $content .= '| Dato: | '.$newBookingDate.' |
';
+ $content .= '| Tid: | '.$newStartTime.' - '.$finishTime.' |
';
+ $content .= '| Tjenester: | ';
+ foreach($serviceArray as $s){ $content .= '| '.$s->service_name_no.' | '.$s->service_price.' kr | '; }
+ $content .= ' |
| Totalpris: | '.$totalServicePrice.' kr |
';
+ $content .= '
Administrer bestilling';
+ $content .= '
STUDIOBEVE';
+ break;
+ case 'hu':
+ $subject = '[studiobeve] A foglalásod módosítva lett';
+ $content = 'Kedves Vendégünk,
A foglalásod módosítva lett.
';
+ $content .= '';
+ $content .= '| Dolgozó: | '.$worker->worker_name.' |
';
+ $content .= '| Dátum: | '.$newBookingDate.' |
';
+ $content .= '| Időpont: | '.$newStartTime.' - '.$finishTime.' |
';
+ $content .= '| Szolgáltatás(ok): | ';
+ foreach($serviceArray as $s){ $content .= '| '.$s->service_name_hu.' | '.$s->service_price.' kr | '; }
+ $content .= ' |
| Összesen: | '.$totalServicePrice.' kr |
';
+ $content .= '
Foglalás kezelése';
+ $content .= '
STUDIOBEVE';
+ break;
+ default:
+ $subject = '[studiobeve] Your booking has been updated';
+ $content = 'Dear Guest,
Your booking has been updated.
';
+ $content .= '';
+ $content .= '| Worker: | '.$worker->worker_name.' |
';
+ $content .= '| Date: | '.$newBookingDate.' |
';
+ $content .= '| Time: | '.$newStartTime.' - '.$finishTime.' |
';
+ $content .= '| Services: | ';
+ foreach($serviceArray as $s){ $content .= '| '.$s->service_name_en.' | '.$s->service_price.' kr | '; }
+ $content .= ' |
| Total price: | '.$totalServicePrice.' kr |
';
+ $content .= '
Manage booking';
+ $content .= '
STUDIOBEVE';
+ }
+
+ $emailArray = array(array(
+ 'addressee_email' => $booking->guest_email,
+ 'addressee_name' => $booking->guest_name,
+ 'subject' => $subject,
+ 'content' => $content,
+ 'attachments' => array()
+ ));
+ $this->load->model('User_model');
+ $this->User_model->sendEmail($emailArray, $lang, '');
+
+ header('Location:'.SITEURL.$lang.'/booking-finished/'.$subpage);
+ }
+
+ public function manage_booking_cancel(){
+ $this->load->helper('url');
+ $this->load->model('Service_model');
+ $this->load->model('User_model');
+
+ if(!isset($_POST['token'])){
+ header('Location:'.SITEURL);
+ return;
+ }
+
+ $token = $_POST['token'];
+ $lang = isset($_POST['lang']) ? $_POST['lang'] : 'en';
+ $booking = $this->Service_model->getBookingByToken($token);
+ if(!$booking){
+ show_404();
+ return;
+ }
+
+ $now = new DateTime('now', new DateTimeZone('Europe/Oslo'));
+ $appointmentDT = new DateTime($booking->booking_date.' '.$booking->booking_start_time, new DateTimeZone('Europe/Oslo'));
+ if(($appointmentDT->getTimestamp() - $now->getTimestamp()) < 86400){
+ $this->output->set_status_header(403)->set_content_type('application/json')
+ ->set_output(json_encode(['error' => 'Cancellation cutoff has passed.']));
+ return;
+ }
+
+ $worker = $this->Service_model->getWorkerById($booking->worker_id);
+
+ switch($lang){
+ case 'no':
+ $subject = '[studiobeve] Din bestilling er avbestilt';
+ $content = 'Kjære gjest,
Din reservasjon den '.$booking->booking_date.' kl. '.date('H:i', strtotime($booking->booking_start_time)).' med '.$worker->worker_name.' er avbestilt.
STUDIOBEVE';
+ break;
+ case 'hu':
+ $subject = '[studiobeve] A foglalásod törölve lett';
+ $content = 'Kedves Vendégünk,
A '.$booking->booking_date.' '.$booking->booking_start_time.'-os foglalásod '.$worker->worker_name.'-nál törölve lett.
STUDIOBEVE';
+ break;
+ default:
+ $subject = '[studiobeve] Your booking has been cancelled';
+ $content = 'Dear Guest,
Your booking on '.$booking->booking_date.' at '.date('H:i', strtotime($booking->booking_start_time)).' with '.$worker->worker_name.' has been cancelled.
STUDIOBEVE';
+ }
+
+ $emailArray = array(array(
+ 'addressee_email' => $booking->guest_email,
+ 'addressee_name' => $booking->guest_name,
+ 'subject' => $subject,
+ 'content' => $content,
+ 'attachments' => array()
+ ));
+ $this->User_model->sendEmail($emailArray, $lang, '');
+
+ $this->Service_model->deleteBooking($booking->booking_id);
+
+ $data['selectedLang'] = $lang;
+ $data['booking'] = $booking;
+ $data['worker'] = $worker;
+ $data['pageTitle'] = '';
+ $this->load->view('pages/manage-booking-cancelled', $data);
+ }
+
}
diff --git a/application/models/Service_model.php b/application/models/Service_model.php
index a88d79a..009c5c7 100755
--- a/application/models/Service_model.php
+++ b/application/models/Service_model.php
@@ -531,15 +531,16 @@ class Service_model extends CI_Model {
$this->load->database();
$query = $this->db->query("INSERT INTO bookings (
guest_name,
- guest_email,
- guest_phone,
+ guest_email,
+ guest_phone,
worker_id,
booking_date,
booking_start_time,
booking_finish_time,
service_ids,
guest_confirmed,
- guest_confirm_code
+ guest_confirm_code,
+ manage_token
) VALUES(
'".$bookingArray['guest_name']."',
'".$bookingArray['guest_email']."',
@@ -550,9 +551,16 @@ class Service_model extends CI_Model {
'".$bookingArray['booking_finish_time']."',
'".$bookingArray['service_ids']."',
'".$bookingArray['guest_confirmed']."',
- '".$bookingArray['guest_confirm_code']."'
+ '".$bookingArray['guest_confirm_code']."',
+ '".$bookingArray['manage_token']."'
);");
}
+
+ public function getBookingByToken($token){
+ $this->load->database();
+ $query = $this->db->query('SELECT * FROM bookings WHERE manage_token = "'.$this->db->escape_str($token).'" LIMIT 1;');
+ return $query->num_rows() > 0 ? $query->result()[0] : false;
+ }
public function getWorkerSchedule($worker_id) {
return $this->db
diff --git a/application/views/pages/manage-booking-cancelled.php b/application/views/pages/manage-booking-cancelled.php
new file mode 100644
index 0000000..5713ce9
--- /dev/null
+++ b/application/views/pages/manage-booking-cancelled.php
@@ -0,0 +1,44 @@
+service_ids);
+ $subpage = 'barber';
+ if(is_array($serviceIds) && !empty($serviceIds)){
+ $firstService = $this->Service_model->getServiceById($serviceIds[0]);
+ if($firstService) $subpage = $firstService->service_type;
+ }
+
+ if($subpage == 'beauty'){
+ include(getcwd().'/application/views/includes/beauty-skeleton-top.php');
+ } else {
+ include(getcwd().'/application/views/includes/barber-skeleton-top.php');
+ }
+
+ $messages = array(
+ 'no' => 'Din bestilling den '.$booking->booking_date.' kl. '.date('H:i', strtotime($booking->booking_start_time)).' med '.$worker->worker_name.' er avbestilt.',
+ 'en' => 'Your booking on '.$booking->booking_date.' at '.date('H:i', strtotime($booking->booking_start_time)).' with '.$worker->worker_name.' has been cancelled.',
+ 'hu' => 'A '.$booking->booking_date.' '.date('H:i', strtotime($booking->booking_start_time)).'-os foglalásod '.$worker->worker_name.'-nál törölve lett.',
+ );
+
+ $titles = array(
+ 'no' => 'Bestilling avbestilt',
+ 'en' => 'Booking cancelled',
+ 'hu' => 'Foglalás törölve',
+ );
+
+ $lang = $selectedLang;
+ $msg = isset($messages[$lang]) ? $messages[$lang] : $messages['en'];
+ $title = isset($titles[$lang]) ? $titles[$lang] : $titles['en'];
+?>
+
+
+
+
diff --git a/application/views/pages/manage-booking.php b/application/views/pages/manage-booking.php
new file mode 100644
index 0000000..06c6769
--- /dev/null
+++ b/application/views/pages/manage-booking.php
@@ -0,0 +1,637 @@
+ 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'];
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ booking_date; ?> |
+
+
+ |
+ booking_start_time)); ?> – booking_finish_time)); ?> |
+
+
+ |
+ worker_name); ?> |
+
+
+ |
+
+ service_ids);
+ if(is_array($serviceIds)){
+ echo '';
+ 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 '- '.htmlspecialchars($name).' — '.$svc->service_price.' kr
';
+ }
+ }
+ echo ' ';
+ }
+ ?>
+ |
+
+
+ |
+ kr |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/css/style.css b/assets/css/style.css
index 6d6f956..1a1e965 100755
--- a/assets/css/style.css
+++ b/assets/css/style.css
@@ -533,7 +533,7 @@
.bookingResultsWrapper{
float: left;
- width: 378px;
+ width: 360px;
margin-left: 10px;
margin-top: 7px;
}