Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce7400578a | ||
|
|
13e1b1a672 | ||
|
|
4c823f92ca | ||
|
|
1a364f21b3 | ||
|
|
06088aee8b | ||
|
|
baa597a7c5 | ||
|
|
35115404be | ||
|
|
7f51a795a5 | ||
|
|
de38aae8d5 |
@@ -89,7 +89,7 @@ $autoload['drivers'] = array();
|
|||||||
|
|
|
|
||||||
| $autoload['helper'] = array('url', 'file');
|
| $autoload['helper'] = array('url', 'file');
|
||||||
*/
|
*/
|
||||||
$autoload['helper'] = array('vertical');
|
$autoload['helper'] = array('vertical', 'asset');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
| -------------------------------------------------------------------
|
| -------------------------------------------------------------------
|
||||||
|
|||||||
@@ -547,6 +547,13 @@ class Admin extends CI_Controller {
|
|||||||
$data['selectedItem'] = $this->Service_model->getWorkerById($worker_id);
|
$data['selectedItem'] = $this->Service_model->getWorkerById($worker_id);
|
||||||
// Verticals are derived from the worker's service category, not stored.
|
// Verticals are derived from the worker's service category, not stored.
|
||||||
$data['workerVerticals'] = $this->Service_model->getVerticalsForWorker($worker_id);
|
$data['workerVerticals'] = $this->Service_model->getVerticalsForWorker($worker_id);
|
||||||
|
|
||||||
|
// Capability grid: which of the category's services this worker performs.
|
||||||
|
// An empty stored set means unrestricted, which the view renders as
|
||||||
|
// everything ticked - see the note in the form.
|
||||||
|
$data['categoryServices'] = $this->Service_model->getServicesByCategoryId($data['selectedItem']->service_category_id);
|
||||||
|
$data['workerServiceIds'] = $this->Service_model->getWorkerServiceIds($worker_id);
|
||||||
|
|
||||||
$this->load->view('admin/update-worker', $data);
|
$this->load->view('admin/update-worker', $data);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
@@ -554,6 +561,48 @@ class Admin extends CI_Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate the PHP upload error code for the worker profile picture into
|
||||||
|
* an admin-facing message.
|
||||||
|
*
|
||||||
|
* The upload blocks below used to gate on tmp_name alone, which made a
|
||||||
|
* REJECTED upload indistinguishable from "no file chosen": PHP empties
|
||||||
|
* tmp_name in both cases, so an oversized photo was skipped in silence and
|
||||||
|
* the worker was saved with an empty worker_profile_img. Reading the error
|
||||||
|
* code is the only way to tell the two apart.
|
||||||
|
*
|
||||||
|
* @return string '' when there is nothing to report (no file chosen, or a
|
||||||
|
* file that arrived intact), otherwise the message to show.
|
||||||
|
*/
|
||||||
|
private function _workerImgUploadError(){
|
||||||
|
// No file part at all - nothing was submitted, nothing to report.
|
||||||
|
if(!isset($_FILES['worker_img']) || !isset($_FILES['worker_img']['error'])){
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch($_FILES['worker_img']['error']){
|
||||||
|
case UPLOAD_ERR_OK:
|
||||||
|
case UPLOAD_ERR_NO_FILE:
|
||||||
|
// Intact, or the admin simply left the field empty.
|
||||||
|
return '';
|
||||||
|
|
||||||
|
case UPLOAD_ERR_INI_SIZE:
|
||||||
|
case UPLOAD_ERR_FORM_SIZE:
|
||||||
|
return 'A kép túl nagy! A megengedett legnagyobb méret: '.ini_get('upload_max_filesize').'.';
|
||||||
|
|
||||||
|
case UPLOAD_ERR_PARTIAL:
|
||||||
|
return 'A kép csak részben töltődött fel. Kérlek próbáld újra!';
|
||||||
|
|
||||||
|
case UPLOAD_ERR_NO_TMP_DIR:
|
||||||
|
case UPLOAD_ERR_CANT_WRITE:
|
||||||
|
case UPLOAD_ERR_EXTENSION:
|
||||||
|
return 'A kép feltöltése szerverhiba miatt nem sikerült. Szólj az üzemeltetőnek!';
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 'Ismeretlen hiba a kép feltöltése közben.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function worker_process(){
|
public function worker_process(){
|
||||||
$this->load->helper('url');
|
$this->load->helper('url');
|
||||||
$this->load->model('User_model');
|
$this->load->model('User_model');
|
||||||
@@ -566,6 +615,15 @@ class Admin extends CI_Controller {
|
|||||||
$data['message'] = '';
|
$data['message'] = '';
|
||||||
$data['message_class'] = '';
|
$data['message_class'] = '';
|
||||||
|
|
||||||
|
// A POST body larger than post_max_size is discarded WHOLESALE by PHP:
|
||||||
|
// $_POST and $_FILES both arrive empty, so none of the branches below
|
||||||
|
// fire and the form appears to do nothing at all. CONTENT_LENGTH still
|
||||||
|
// reports the real size, which is the only surviving evidence.
|
||||||
|
if($_SERVER['REQUEST_METHOD'] === 'POST' && empty($_POST) && !empty($_SERVER['CONTENT_LENGTH'])){
|
||||||
|
$data['message'] = 'A beküldött adat túl nagy volt, ezért a mentés nem történt meg. A kép legfeljebb '.ini_get('upload_max_filesize').' lehet.';
|
||||||
|
$data['message_class'] = 'errorMessage';
|
||||||
|
}
|
||||||
|
|
||||||
if(isset($_GET['delete-worker'])){
|
if(isset($_GET['delete-worker'])){
|
||||||
$selected_worker = $this->Service_model->getWorkerById($_GET['delete-worker']);
|
$selected_worker = $this->Service_model->getWorkerById($_GET['delete-worker']);
|
||||||
$workerArray = array(
|
$workerArray = array(
|
||||||
@@ -581,7 +639,12 @@ class Admin extends CI_Controller {
|
|||||||
|
|
||||||
$data['worker_img'] = '';
|
$data['worker_img'] = '';
|
||||||
|
|
||||||
if($_FILES["worker_img"]["tmp_name"] != ''){
|
$workerImgError = $this->_workerImgUploadError();
|
||||||
|
|
||||||
|
if($workerImgError !== ''){
|
||||||
|
$data['message'] = $workerImgError;
|
||||||
|
$data['message_class'] = 'errorMessage';
|
||||||
|
} elseif($_FILES["worker_img"]["tmp_name"] != ''){
|
||||||
$target_dir = getcwd()."/assets/img/";
|
$target_dir = getcwd()."/assets/img/";
|
||||||
$target_file = $target_dir . 'worker_'.str_replace(' ','_', $_POST['worker_name']);
|
$target_file = $target_dir . 'worker_'.str_replace(' ','_', $_POST['worker_name']);
|
||||||
$imageFileType = strtolower(pathinfo(basename($_FILES["worker_img"]["name"]),PATHINFO_EXTENSION));
|
$imageFileType = strtolower(pathinfo(basename($_FILES["worker_img"]["name"]),PATHINFO_EXTENSION));
|
||||||
@@ -632,7 +695,44 @@ class Admin extends CI_Controller {
|
|||||||
$this->Service_model->updateWorker($_POST['worker_id'], $workerArray);
|
$this->Service_model->updateWorker($_POST['worker_id'], $workerArray);
|
||||||
$this->Log_model->addLog('workers', $data['currentUser']->username.' módosította a(z) '.$_POST['worker_name'].' nevű dolgozót ('.implode(',',$workerArray).')', $data['currentUser']->username);
|
$this->Log_model->addLog('workers', $data['currentUser']->username.' módosította a(z) '.$_POST['worker_name'].' nevű dolgozót ('.implode(',',$workerArray).')', $data['currentUser']->username);
|
||||||
|
|
||||||
if($_FILES["worker_img"]["tmp_name"] != ''){
|
// Capability set. The grid posts the ticked service ids; anything not
|
||||||
|
// belonging to the worker's (possibly just-changed) category is dropped
|
||||||
|
// so a category switch cannot leave orphaned rows behind.
|
||||||
|
$postedServiceIds = isset($_POST['worker_services']) && is_array($_POST['worker_services'])
|
||||||
|
? $_POST['worker_services']
|
||||||
|
: array();
|
||||||
|
|
||||||
|
$categoryServices = $this->Service_model->getServicesByCategoryId($_POST['service_category_id']);
|
||||||
|
$categoryServiceIds = array();
|
||||||
|
if(is_array($categoryServices)){
|
||||||
|
foreach($categoryServices as $svc){
|
||||||
|
$categoryServiceIds[] = (int)$svc->service_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedServiceIds = array();
|
||||||
|
foreach($postedServiceIds as $postedId){
|
||||||
|
if(in_array((int)$postedId, $categoryServiceIds, TRUE)){
|
||||||
|
$allowedServiceIds[] = (int)$postedId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every service ticked means "no restriction": store nothing, so the
|
||||||
|
// worker keeps performing services added in the future too. Storing the
|
||||||
|
// full set instead would silently freeze them out of every new service.
|
||||||
|
if(count($allowedServiceIds) === count($categoryServiceIds)){
|
||||||
|
$allowedServiceIds = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->Service_model->setWorkerServiceIds($_POST['worker_id'], $allowedServiceIds);
|
||||||
|
$this->Log_model->addLog('workers', $data['currentUser']->username.' beállította a(z) '.$_POST['worker_name'].' nevű dolgozó végezhető szolgáltatásait ('.(empty($allowedServiceIds) ? 'nincs korlátozás' : implode(',', $allowedServiceIds)).')', $data['currentUser']->username);
|
||||||
|
|
||||||
|
$workerImgError = $this->_workerImgUploadError();
|
||||||
|
|
||||||
|
if($workerImgError !== ''){
|
||||||
|
$data['message'] = $workerImgError;
|
||||||
|
$data['message_class'] = 'errorMessage';
|
||||||
|
} elseif($_FILES["worker_img"]["tmp_name"] != ''){
|
||||||
$target_dir = getcwd()."/assets/img/";
|
$target_dir = getcwd()."/assets/img/";
|
||||||
$target_file = $target_dir . 'worker_'.str_replace(' ','_', $_POST['worker_name']);
|
$target_file = $target_dir . 'worker_'.str_replace(' ','_', $_POST['worker_name']);
|
||||||
$imageFileType = strtolower(pathinfo(basename($_FILES["worker_img"]["name"]),PATHINFO_EXTENSION));
|
$imageFileType = strtolower(pathinfo(basename($_FILES["worker_img"]["name"]),PATHINFO_EXTENSION));
|
||||||
|
|||||||
@@ -137,7 +137,15 @@ class Pages extends CI_Controller {
|
|||||||
$data['pageTitle'] = '';
|
$data['pageTitle'] = '';
|
||||||
|
|
||||||
if(isset($_POST['action']) && $_POST['action'] == 'getAvailableWorkersByServiceCategory' && isset($_POST['serv_cat_slug'])){
|
if(isset($_POST['action']) && $_POST['action'] == 'getAvailableWorkersByServiceCategory' && isset($_POST['serv_cat_slug'])){
|
||||||
$workers = $this->Service_model->getWorkersByCategorySlug($_POST['serv_cat_slug']);
|
// The picker must only offer workers who can perform EVERY service the
|
||||||
|
// guest has ticked. service_ids is supplied by the booking form; when it
|
||||||
|
// is absent (older cached JS) the call degrades to the category-only
|
||||||
|
// behaviour rather than returning nobody.
|
||||||
|
$requestedServiceIds = isset($_POST['service_ids']) && is_array($_POST['service_ids'])
|
||||||
|
? $_POST['service_ids']
|
||||||
|
: array();
|
||||||
|
|
||||||
|
$workers = $this->Service_model->getWorkersByCategorySlug($_POST['serv_cat_slug'], $requestedServiceIds);
|
||||||
$returnedWorkers = '';
|
$returnedWorkers = '';
|
||||||
$workerIndex = 0;
|
$workerIndex = 0;
|
||||||
$workerListShow = '';
|
$workerListShow = '';
|
||||||
@@ -307,7 +315,7 @@ class Pages extends CI_Controller {
|
|||||||
* form is a normal full-page POST, so whatever this outputs is what the
|
* form is a normal full-page POST, so whatever this outputs is what the
|
||||||
* customer actually sees in their browser.
|
* customer actually sees in their browser.
|
||||||
*/
|
*/
|
||||||
private function _booking_error($errorCode, $statusCode = 409){
|
private function _booking_error($errorCode, $statusCode = 409, $backUrl = ''){
|
||||||
$this->load->helper('url');
|
$this->load->helper('url');
|
||||||
|
|
||||||
$lang = vertical_lang(isset($_POST['lang']) ? $_POST['lang'] : '');
|
$lang = vertical_lang(isset($_POST['lang']) ? $_POST['lang'] : '');
|
||||||
@@ -324,7 +332,10 @@ class Pages extends CI_Controller {
|
|||||||
'selectedLang' => $lang,
|
'selectedLang' => $lang,
|
||||||
'subpage' => $vertical['slug'],
|
'subpage' => $vertical['slug'],
|
||||||
'vertical' => $vertical,
|
'vertical' => $vertical,
|
||||||
'bookingErrorCode' => $errorCode
|
'bookingErrorCode' => $errorCode,
|
||||||
|
// Manage-booking errors must return the guest to their own manage page,
|
||||||
|
// not to a fresh booking form. Empty = the new-booking default.
|
||||||
|
'bookingBackUrl' => $backUrl
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->output->set_status_header($statusCode);
|
$this->output->set_status_header($statusCode);
|
||||||
@@ -404,6 +415,23 @@ class Pages extends CI_Controller {
|
|||||||
'manage_token' => $manageToken,
|
'manage_token' => $manageToken,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Reject an unusable e-mail BEFORE anything is written. The booking is
|
||||||
|
// saved well before sendEmail() runs, so without this the row is created,
|
||||||
|
// PHPMailer's addAddress() throws, and the guest is redirected to the
|
||||||
|
// success page having received nothing - no confirmation and no manage
|
||||||
|
// link. Evelin is a CC on that same message, so the salon is not told
|
||||||
|
// either. filter_var is equal-or-stricter than PHPMailer's own check, so
|
||||||
|
// anything accepted here will not throw later.
|
||||||
|
//
|
||||||
|
// Public flow only: admin-created block bookings legitimately carry an
|
||||||
|
// empty guest_email and go through Admin::booking_process().
|
||||||
|
$guestEmailInput = isset($_POST['guest_email']) ? trim($_POST['guest_email']) : '';
|
||||||
|
if ($guestEmailInput === '' || !filter_var($guestEmailInput, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$this->_booking_error('invalid_email', 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$bookingArray['guest_email'] = $guestEmailInput;
|
||||||
|
|
||||||
$bookingDate = $bookingArray['booking_date'];
|
$bookingDate = $bookingArray['booking_date'];
|
||||||
$bookingTime = $bookingArray['booking_start_time'];
|
$bookingTime = $bookingArray['booking_start_time'];
|
||||||
|
|
||||||
@@ -428,6 +456,15 @@ class Pages extends CI_Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per-worker capability. The category check above only proves the
|
||||||
|
// services belong to the worker's category - not that this particular
|
||||||
|
// worker performs them. The picker already hides incapable workers, but
|
||||||
|
// that is a UI affordance, so re-check before the booking is written.
|
||||||
|
if (!$this->Service_model->workerCanPerformServices($bookingArray['worker_id'], $selectedServiceArray)) {
|
||||||
|
$this->_booking_error('service_not_offered', 403);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Single-service verticals: the client disables the other checkboxes,
|
// Single-service verticals: the client disables the other checkboxes,
|
||||||
// but that is a UI affordance only. The vertical is derived from the
|
// but that is a UI affordance only. The vertical is derived from the
|
||||||
// services themselves rather than $_POST['subpage'], which is
|
// services themselves rather than $_POST['subpage'], which is
|
||||||
@@ -820,10 +857,14 @@ class Pages extends CI_Controller {
|
|||||||
// wrong vertical's branding.
|
// wrong vertical's branding.
|
||||||
$serviceIds = unserialize($booking->service_ids);
|
$serviceIds = unserialize($booking->service_ids);
|
||||||
$subpage = 'barber';
|
$subpage = 'barber';
|
||||||
|
$firstService = NULL;
|
||||||
if(is_array($serviceIds)){
|
if(is_array($serviceIds)){
|
||||||
foreach($serviceIds as $serviceIdItem){
|
foreach($serviceIds as $serviceIdItem){
|
||||||
$resolvedService = $this->Service_model->getServiceById($serviceIdItem);
|
$resolvedService = $this->Service_model->getServiceById($serviceIdItem);
|
||||||
if($resolvedService && $resolvedService->service_type !== ''){
|
if($resolvedService && $resolvedService->service_type !== ''){
|
||||||
|
// Kept for the worker filter below: the category (and therefore the
|
||||||
|
// capability check) is derived from this same resolved service.
|
||||||
|
$firstService = $resolvedService;
|
||||||
$subpage = $resolvedService->service_type;
|
$subpage = $resolvedService->service_type;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -859,14 +900,14 @@ class Pages extends CI_Controller {
|
|||||||
// Filter workers to those qualified for the booking's service category,
|
// Filter workers to those qualified for the booking's service category,
|
||||||
// so the guest can't switch to a worker who doesn't perform these services.
|
// so the guest can't switch to a worker who doesn't perform these services.
|
||||||
$categorySlug = '';
|
$categorySlug = '';
|
||||||
if(isset($firstService) && $firstService){
|
if($firstService){
|
||||||
$category = $this->Service_model->getServiceCategoryById($firstService->service_category_id);
|
$category = $this->Service_model->getServiceCategoryById($firstService->service_category_id);
|
||||||
if($category){
|
if($category){
|
||||||
$categorySlug = $category->serv_cat_slug;
|
$categorySlug = $category->serv_cat_slug;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if($categorySlug !== ''){
|
if($categorySlug !== ''){
|
||||||
$data['workers'] = $this->Service_model->getWorkersByCategorySlug($categorySlug);
|
$data['workers'] = $this->Service_model->getWorkersByCategorySlug($categorySlug, is_array($serviceIds) ? $serviceIds : array());
|
||||||
} else {
|
} else {
|
||||||
$data['workers'] = $this->Service_model->getActiveWorkers($subpage);
|
$data['workers'] = $this->Service_model->getActiveWorkers($subpage);
|
||||||
}
|
}
|
||||||
@@ -894,11 +935,17 @@ class Pages extends CI_Controller {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This form is a normal POST, not AJAX, so every rejection below is rendered
|
||||||
|
// as the shared booking error page. Returning raw JSON showed the guest an
|
||||||
|
// unstyled {"error":...} blob after they had filled the whole form in.
|
||||||
|
// Use the stored token, not the posted one: it is echoed straight into the
|
||||||
|
// error page's back link.
|
||||||
|
$manageBackUrl = SITEURL.vertical_lang(isset($_POST['lang']) ? $_POST['lang'] : '').'/manage-booking/'.$booking->manage_token;
|
||||||
|
|
||||||
$now = new DateTime('now', new DateTimeZone('Europe/Oslo'));
|
$now = new DateTime('now', new DateTimeZone('Europe/Oslo'));
|
||||||
$appointmentDT = new DateTime($booking->booking_date.' '.$booking->booking_start_time, new DateTimeZone('Europe/Oslo'));
|
$appointmentDT = new DateTime($booking->booking_date.' '.$booking->booking_start_time, new DateTimeZone('Europe/Oslo'));
|
||||||
if(($appointmentDT->getTimestamp() - $now->getTimestamp()) < 86400){
|
if(($appointmentDT->getTimestamp() - $now->getTimestamp()) < 86400){
|
||||||
$this->output->set_status_header(403)->set_content_type('application/json')
|
$this->_booking_error('cutoff_passed', 403, $manageBackUrl);
|
||||||
->set_output(json_encode(['error' => 'Modification cutoff has passed.']));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -929,27 +976,31 @@ class Pages extends CI_Controller {
|
|||||||
if(!empty($selectedServiceArray) && $submittedWorker){
|
if(!empty($selectedServiceArray) && $submittedWorker){
|
||||||
$firstSelectedService = $this->Service_model->getServiceById($selectedServiceArray[0]);
|
$firstSelectedService = $this->Service_model->getServiceById($selectedServiceArray[0]);
|
||||||
if($firstSelectedService && $firstSelectedService->service_category_id != $submittedWorker->service_category_id){
|
if($firstSelectedService && $firstSelectedService->service_category_id != $submittedWorker->service_category_id){
|
||||||
$this->output->set_status_header(403)->set_content_type('application/json')
|
$this->_booking_error('category_mismatch', 403, $manageBackUrl);
|
||||||
->set_output(json_encode(['error' => 'Selected worker does not perform the chosen services.']));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per-worker capability, same reasoning as booking_process(): the
|
||||||
|
// category check proves only that the services belong to the worker's
|
||||||
|
// category, not that this worker performs them.
|
||||||
|
if(!$this->Service_model->workerCanPerformServices($newWorkerId, $selectedServiceArray)){
|
||||||
|
$this->_booking_error('service_not_offered', 403, $manageBackUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Schedule check
|
// Schedule check
|
||||||
$schedule = $this->Service_model->getWorkerScheduleForDate($newWorkerId, $newBookingDate);
|
$schedule = $this->Service_model->getWorkerScheduleForDate($newWorkerId, $newBookingDate);
|
||||||
if(!$schedule){
|
if(!$schedule){
|
||||||
$this->output->set_status_header(403)->set_content_type('application/json')
|
$this->_booking_error('worker_unavailable', 403, $manageBackUrl);
|
||||||
->set_output(json_encode(['error' => 'Worker is not available on this day.']));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if($newStartTime < $schedule->start_time || $newStartTime >= $schedule->end_time){
|
if($newStartTime < $schedule->start_time || $newStartTime >= $schedule->end_time){
|
||||||
$this->output->set_status_header(403)->set_content_type('application/json')
|
$this->_booking_error('outside_schedule', 403, $manageBackUrl);
|
||||||
->set_output(json_encode(['error' => 'Booking time is outside worker schedule.']));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!$this->Service_model->isWorkerAvailableThisWeek($newWorkerId, $newBookingDate)){
|
if(!$this->Service_model->isWorkerAvailableThisWeek($newWorkerId, $newBookingDate)){
|
||||||
$this->output->set_status_header(403)->set_content_type('application/json')
|
$this->_booking_error('alternate_week', 403, $manageBackUrl);
|
||||||
->set_output(json_encode(['error' => 'Worker only works every second week.']));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -957,8 +1008,7 @@ class Pages extends CI_Controller {
|
|||||||
$available = $this->Service_model->getAvailableTimes($newWorkerId, $newBookingDate, $servicelength, $booking->booking_id);
|
$available = $this->Service_model->getAvailableTimes($newWorkerId, $newBookingDate, $servicelength, $booking->booking_id);
|
||||||
|
|
||||||
if(!is_array($available) || !in_array($newStartTime, $available)){
|
if(!is_array($available) || !in_array($newStartTime, $available)){
|
||||||
$this->output->set_status_header(409)->set_content_type('application/json')
|
$this->_booking_error('slot_taken', 409, $manageBackUrl);
|
||||||
->set_output(json_encode(['error' => 'Conflict. Timeslot is taken or does not fit the service.']));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,8 +1017,7 @@ class Pages extends CI_Controller {
|
|||||||
$maxDate = (clone $today)->modify('+3 months');
|
$maxDate = (clone $today)->modify('+3 months');
|
||||||
$selectedDate = new DateTime($newBookingDate);
|
$selectedDate = new DateTime($newBookingDate);
|
||||||
if($selectedDate > $maxDate){
|
if($selectedDate > $maxDate){
|
||||||
$this->output->set_status_header(403)->set_content_type('application/json')
|
$this->_booking_error('too_far', 403, $manageBackUrl);
|
||||||
->set_output(json_encode(['error' => 'Bookings can only be made up to 3 months in advance.']));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -980,8 +1029,7 @@ class Pages extends CI_Controller {
|
|||||||
$bookingEnd->add(new DateInterval('PT'.intval($bookingLength->format('i')).'M'));
|
$bookingEnd->add(new DateInterval('PT'.intval($bookingLength->format('i')).'M'));
|
||||||
$closingTime = new DateTime('18:00');
|
$closingTime = new DateTime('18:00');
|
||||||
if($bookingEnd > $closingTime){
|
if($bookingEnd > $closingTime){
|
||||||
$this->output->set_status_header(403)->set_content_type('application/json')
|
$this->_booking_error('after_hours', 403, $manageBackUrl);
|
||||||
->set_output(json_encode(['error' => 'Selected time exceeds business hours.']));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$finishTime = $bookingEnd->format('H:i:s');
|
$finishTime = $bookingEnd->format('H:i:s');
|
||||||
@@ -1143,8 +1191,7 @@ class Pages extends CI_Controller {
|
|||||||
$now = new DateTime('now', new DateTimeZone('Europe/Oslo'));
|
$now = new DateTime('now', new DateTimeZone('Europe/Oslo'));
|
||||||
$appointmentDT = new DateTime($booking->booking_date.' '.$booking->booking_start_time, new DateTimeZone('Europe/Oslo'));
|
$appointmentDT = new DateTime($booking->booking_date.' '.$booking->booking_start_time, new DateTimeZone('Europe/Oslo'));
|
||||||
if(($appointmentDT->getTimestamp() - $now->getTimestamp()) < 86400){
|
if(($appointmentDT->getTimestamp() - $now->getTimestamp()) < 86400){
|
||||||
$this->output->set_status_header(403)->set_content_type('application/json')
|
$this->_booking_error('cutoff_passed', 403, SITEURL.vertical_lang($lang).'/manage-booking/'.$booking->manage_token);
|
||||||
->set_output(json_encode(['error' => 'Cancellation cutoff has passed.']));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache-busting asset URLs.
|
||||||
|
*
|
||||||
|
* Apache serves this site's static files with only Last-Modified/ETag and no
|
||||||
|
* Cache-Control or Expires header, so browsers fall back to heuristic freshness
|
||||||
|
* and can hold a stale stylesheet for hours after a deploy. Because deploys here
|
||||||
|
* are a file copy, there is no other signal that anything changed - during
|
||||||
|
* development this repeatedly masked CSS edits, and on the live site a returning
|
||||||
|
* customer would keep the old stylesheet with no way to know.
|
||||||
|
*
|
||||||
|
* Appending the file's mtime makes the URL itself change whenever the file does,
|
||||||
|
* so the browser is forced to refetch. It costs one stat() per asset, which PHP
|
||||||
|
* caches per request.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ( ! function_exists('asset_ver'))
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Absolute URL for a local asset, with ?v=<mtime> appended.
|
||||||
|
*
|
||||||
|
* Only for files inside the webroot. Remote assets (Google Fonts, the
|
||||||
|
* cookieyes script, momentjs) must keep their own URLs untouched.
|
||||||
|
*
|
||||||
|
* Falls back to the plain URL when the file is missing, so a bad path
|
||||||
|
* degrades to the previous behaviour rather than emitting a warning.
|
||||||
|
*
|
||||||
|
* @param string $path webroot-relative, e.g. 'assets/css/main.css'
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function asset_ver($path)
|
||||||
|
{
|
||||||
|
$path = ltrim((string) $path, '/');
|
||||||
|
$url = rtrim(SITEURL, '/').'/'.$path;
|
||||||
|
|
||||||
|
$file = FCPATH.$path;
|
||||||
|
|
||||||
|
if ($path !== '' && is_file($file))
|
||||||
|
{
|
||||||
|
$url .= (strpos($url, '?') === FALSE ? '?' : '&').'v='.filemtime($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -164,10 +164,25 @@ public function getModulesByPermissionSlug($permissionSlug){
|
|||||||
$this->load->model('Module_model');
|
$this->load->model('Module_model');
|
||||||
$selectedPermission = $this->Module_model->getPermissionBySlug($permissionSlug);
|
$selectedPermission = $this->Module_model->getPermissionBySlug($permissionSlug);
|
||||||
|
|
||||||
|
// An unknown slug yields no permission at all; without this the line below
|
||||||
|
// dereferences null. No permission means no modules.
|
||||||
|
if(!$selectedPermission){
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
$query = $this->db->query('SELECT * FROM modules;');
|
$query = $this->db->query('SELECT * FROM modules;');
|
||||||
$results = $query->result();
|
$results = $query->result();
|
||||||
$resultArray = array();
|
$resultArray = array();
|
||||||
foreach($results as $resultItem){
|
foreach($results as $resultItem){
|
||||||
|
// modules.permission_ids is NULL for at least one row (module 8,
|
||||||
|
// 'Szerviz kategoriak') on every environment. Passing null to explode()
|
||||||
|
// is deprecated in PHP 8.1 and emits a notice. A module with no
|
||||||
|
// permissions is visible to nobody, so skipping it keeps the previous
|
||||||
|
// behaviour - explode(',', null) returned array('') which matched nothing.
|
||||||
|
if($resultItem->permission_ids === null || $resultItem->permission_ids === ''){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$permissionIdsArray = explode(',', $resultItem->permission_ids);
|
$permissionIdsArray = explode(',', $resultItem->permission_ids);
|
||||||
if(in_array($selectedPermission->permission_id, $permissionIdsArray)){
|
if(in_array($selectedPermission->permission_id, $permissionIdsArray)){
|
||||||
$resultArray[] = $resultItem;
|
$resultArray[] = $resultItem;
|
||||||
|
|||||||
@@ -127,20 +127,28 @@ class Service_model extends CI_Model {
|
|||||||
|
|
||||||
$this->_applyLegacyVerticalFlags($workerArray, $workerArray['service_category_id']);
|
$this->_applyLegacyVerticalFlags($workerArray, $workerArray['service_category_id']);
|
||||||
|
|
||||||
|
// is_active was previously omitted from this INSERT, so the column
|
||||||
|
// default (1) always won and a worker created as inactive silently
|
||||||
|
// came back active. The admin's choice is honoured here; callers that
|
||||||
|
// do not supply it still get the old default.
|
||||||
|
$is_active = isset($workerArray['is_active']) ? (int)$workerArray['is_active'] : 1;
|
||||||
|
|
||||||
$query = $this->db->query('INSERT INTO workers (
|
$query = $this->db->query('INSERT INTO workers (
|
||||||
worker_name,
|
worker_name,
|
||||||
worker_profile_img,
|
worker_profile_img,
|
||||||
worker_info,
|
worker_info,
|
||||||
is_beauty,
|
is_beauty,
|
||||||
is_barber,
|
is_barber,
|
||||||
service_category_id
|
service_category_id,
|
||||||
) VALUES(?, ?, ?, ?, ?, ?)', array(
|
is_active
|
||||||
|
) VALUES(?, ?, ?, ?, ?, ?, ?)', array(
|
||||||
$workerArray['worker_name'],
|
$workerArray['worker_name'],
|
||||||
$workerArray['worker_profile_img'],
|
$workerArray['worker_profile_img'],
|
||||||
$workerArray['worker_info'],
|
$workerArray['worker_info'],
|
||||||
$workerArray['is_beauty'],
|
$workerArray['is_beauty'],
|
||||||
$workerArray['is_barber'],
|
$workerArray['is_barber'],
|
||||||
$workerArray['service_category_id']
|
$workerArray['service_category_id'],
|
||||||
|
$is_active
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,6 +251,25 @@ class Service_model extends CI_Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Live services belonging to one category, ordered for display as the
|
||||||
|
* admin capability grid (sub-heading, then name).
|
||||||
|
*
|
||||||
|
* @param int $service_category_id
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getServicesByCategoryId($service_category_id){
|
||||||
|
$this->load->database();
|
||||||
|
$query = $this->db->query(
|
||||||
|
'SELECT * FROM services
|
||||||
|
WHERE service_category_id = ? AND is_deleted != "1"
|
||||||
|
ORDER BY service_type ASC, service_category_no ASC, service_id ASC',
|
||||||
|
array($service_category_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $query->result();
|
||||||
|
}
|
||||||
|
|
||||||
public function getAllServiceCategories(){
|
public function getAllServiceCategories(){
|
||||||
$this->load->database();
|
$this->load->database();
|
||||||
$query = $this->db->query('SELECT * FROM service_categories WHERE is_deleted != "1" ORDER BY service_category_id;');
|
$query = $this->db->query('SELECT * FROM service_categories WHERE is_deleted != "1" ORDER BY service_category_id;');
|
||||||
@@ -300,9 +327,161 @@ class Service_model extends CI_Model {
|
|||||||
return $query->result();
|
return $query->result();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getWorkersByCategorySlug($serv_cat_slug){
|
/**
|
||||||
|
* The service ids one worker is allowed to perform.
|
||||||
|
*
|
||||||
|
* An EMPTY result means "unrestricted" - the worker performs every live
|
||||||
|
* service in their category, which is how every worker behaved before
|
||||||
|
* worker_services existed. Restrictions are therefore opt-in per worker,
|
||||||
|
* and a missing migration degrades to the old behaviour instead of
|
||||||
|
* hiding everybody from the booking flow.
|
||||||
|
*
|
||||||
|
* @param int $worker_id
|
||||||
|
* @return array list of service_id ints (empty = unrestricted)
|
||||||
|
*/
|
||||||
|
public function getWorkerServiceIds($worker_id){
|
||||||
$this->load->database();
|
$this->load->database();
|
||||||
$query = $this->db->query('SELECT * FROM workers JOIN service_categories ON workers.service_category_id = service_categories.service_category_id WHERE workers.is_deleted != "1" AND serv_cat_slug = ? ORDER BY worker_name ASC', array($serv_cat_slug));
|
$query = $this->db->query(
|
||||||
|
'SELECT service_id FROM worker_services WHERE worker_id = ? ORDER BY service_id ASC',
|
||||||
|
array($worker_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
$ids = array();
|
||||||
|
foreach($query->result() as $row){
|
||||||
|
$ids[] = (int)$row->service_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace a worker's allowed-service set.
|
||||||
|
*
|
||||||
|
* Passing an empty array clears every row, which restores the worker to
|
||||||
|
* the unrestricted default rather than making them unable to do anything.
|
||||||
|
*
|
||||||
|
* @param int $worker_id
|
||||||
|
* @param array $service_ids
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setWorkerServiceIds($worker_id, $service_ids){
|
||||||
|
$this->load->database();
|
||||||
|
|
||||||
|
$this->db->query('DELETE FROM worker_services WHERE worker_id = ?', array($worker_id));
|
||||||
|
|
||||||
|
if(!is_array($service_ids) || empty($service_ids)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// De-duplicate: the PK would reject repeats and abort the whole save.
|
||||||
|
$clean = array();
|
||||||
|
foreach($service_ids as $service_id){
|
||||||
|
$service_id = (int)$service_id;
|
||||||
|
if($service_id > 0){
|
||||||
|
$clean[$service_id] = TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(array_keys($clean) as $service_id){
|
||||||
|
$this->db->query(
|
||||||
|
'INSERT INTO worker_services (worker_id, service_id) VALUES (?, ?)',
|
||||||
|
array($worker_id, $service_id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can this worker perform EVERY one of these services?
|
||||||
|
*
|
||||||
|
* Used as the server-side guard behind the worker picker: the picker only
|
||||||
|
* offers capable workers, but that is a UI affordance and a crafted POST
|
||||||
|
* must not get past it.
|
||||||
|
*
|
||||||
|
* @param int $worker_id
|
||||||
|
* @param array $service_ids
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function workerCanPerformServices($worker_id, $service_ids){
|
||||||
|
if(!is_array($service_ids) || empty($service_ids)){
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowed = $this->getWorkerServiceIds($worker_id);
|
||||||
|
|
||||||
|
// No rows at all = unrestricted worker.
|
||||||
|
if(empty($allowed)){
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($service_ids as $service_id){
|
||||||
|
if(!in_array((int)$service_id, $allowed, TRUE)){
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bookable workers for one service category, optionally narrowed to those
|
||||||
|
* able to perform a specific set of services.
|
||||||
|
*
|
||||||
|
* Both callers are guest-facing (the booking worker picker and the
|
||||||
|
* manage-booking worker list), so an INACTIVE worker must not appear:
|
||||||
|
* this used to filter on is_deleted alone, which meant switching a
|
||||||
|
* worker to inactive in the admin had no effect on the public flow.
|
||||||
|
* getActiveWorkers() - the fallback used by the very same caller when
|
||||||
|
* no category is known - has always filtered on is_active; the two
|
||||||
|
* paths now agree.
|
||||||
|
*
|
||||||
|
* @param string $serv_cat_slug
|
||||||
|
* @param array $service_ids selected services; empty = no capability filter
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getWorkersByCategorySlug($serv_cat_slug, $service_ids = array()){
|
||||||
|
$this->load->database();
|
||||||
|
|
||||||
|
$sql = 'SELECT w.* FROM workers w
|
||||||
|
JOIN service_categories sc ON w.service_category_id = sc.service_category_id
|
||||||
|
WHERE w.is_deleted != "1" AND w.is_active = "1" AND sc.serv_cat_slug = ?';
|
||||||
|
$params = array($serv_cat_slug);
|
||||||
|
|
||||||
|
// Capability filter. A worker qualifies when they are unrestricted (no
|
||||||
|
// worker_services rows at all) OR their allowed set covers EVERY
|
||||||
|
// selected service - counting matched rows against the number asked
|
||||||
|
// for, so a worker who can do 2 of the 3 chosen services is excluded.
|
||||||
|
$clean = array();
|
||||||
|
if(is_array($service_ids)){
|
||||||
|
foreach($service_ids as $service_id){
|
||||||
|
$service_id = (int)$service_id;
|
||||||
|
if($service_id > 0){
|
||||||
|
$clean[$service_id] = TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$clean = array_keys($clean);
|
||||||
|
|
||||||
|
if(!empty($clean)){
|
||||||
|
$placeholders = implode(',', array_fill(0, count($clean), '?'));
|
||||||
|
|
||||||
|
$sql .= ' AND (
|
||||||
|
NOT EXISTS (SELECT 1 FROM worker_services wsx WHERE wsx.worker_id = w.worker_id)
|
||||||
|
OR (
|
||||||
|
SELECT COUNT(*) FROM worker_services ws
|
||||||
|
WHERE ws.worker_id = w.worker_id
|
||||||
|
AND ws.service_id IN ('.$placeholders.')
|
||||||
|
) = ?
|
||||||
|
)';
|
||||||
|
|
||||||
|
foreach($clean as $service_id){
|
||||||
|
$params[] = $service_id;
|
||||||
|
}
|
||||||
|
$params[] = count($clean);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= ' ORDER BY w.worker_name ASC';
|
||||||
|
|
||||||
|
$query = $this->db->query($sql, $params);
|
||||||
return $query->result();
|
return $query->result();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,6 +801,10 @@ class Service_model extends CI_Model {
|
|||||||
$finishTime = new DateTime($selectedDate . ' ' . $schedule->end_time, new DateTimeZone('Europe/Oslo'));
|
$finishTime = new DateTime($selectedDate . ' ' . $schedule->end_time, new DateTimeZone('Europe/Oslo'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A műszak valódi kezdete - $dayStartTime-ot a mai napra alább felülírjuk a legkorábban
|
||||||
|
// foglalható időponttal, az ebédszünet számítása viszont a tényleges műszakkal dolgozik.
|
||||||
|
$shiftStartTime = clone $dayStartTime;
|
||||||
|
|
||||||
// --- ÚJ: ma foglalva csak (következő 15 perces blokk + 1 óra) UTÁN legyen időpont ---
|
// --- ÚJ: ma foglalva csak (következő 15 perces blokk + 1 óra) UTÁN legyen időpont ---
|
||||||
if ($selectedDate == date('Y-m-d')) {
|
if ($selectedDate == date('Y-m-d')) {
|
||||||
// használd a helyi (Oslo) időt, ne az esetleges UTC defaultot
|
// használd a helyi (Oslo) időt, ne az esetleges UTC defaultot
|
||||||
@@ -648,7 +831,7 @@ class Service_model extends CI_Model {
|
|||||||
// --- Lunch break setup ---
|
// --- Lunch break setup ---
|
||||||
// Pre-fetch all bookings for the day so we can do hypothetical checks per slot
|
// Pre-fetch all bookings for the day so we can do hypothetical checks per slot
|
||||||
$workerRow = $this->db->query("SELECT lunch_window_start, lunch_window_end, lunch_preferred_time FROM workers WHERE worker_id = ? LIMIT 1", array($worker_id))->row();
|
$workerRow = $this->db->query("SELECT lunch_window_start, lunch_window_end, lunch_preferred_time FROM workers WHERE worker_id = ? LIMIT 1", array($worker_id))->row();
|
||||||
$shiftSeconds = $finishTime->getTimestamp() - $dayStartTime->getTimestamp();
|
$shiftSeconds = $finishTime->getTimestamp() - $shiftStartTime->getTimestamp();
|
||||||
$lunchRequired = $workerRow && $workerRow->lunch_window_start && $workerRow->lunch_window_end && $shiftSeconds >= 6 * 3600;
|
$lunchRequired = $workerRow && $workerRow->lunch_window_start && $workerRow->lunch_window_end && $shiftSeconds >= 6 * 3600;
|
||||||
|
|
||||||
$allBookings = [];
|
$allBookings = [];
|
||||||
@@ -705,7 +888,7 @@ class Service_model extends CI_Model {
|
|||||||
]]);
|
]]);
|
||||||
$canStillBreak = $this->computeLunchBreak(
|
$canStillBreak = $this->computeLunchBreak(
|
||||||
$workerRow->lunch_window_start, $workerRow->lunch_window_end,
|
$workerRow->lunch_window_start, $workerRow->lunch_window_end,
|
||||||
$selectedDate, $hypo, $finishTime, $dayStartTime,
|
$selectedDate, $hypo, $finishTime, $shiftStartTime,
|
||||||
$workerRow->lunch_preferred_time
|
$workerRow->lunch_preferred_time
|
||||||
) !== null;
|
) !== null;
|
||||||
if (!$canStillBreak) continue; // booking this slot would eliminate the only break opportunity
|
if (!$canStillBreak) continue; // booking this slot would eliminate the only break opportunity
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ class User_model extends CI_Model {
|
|||||||
public function sendEmail($addressList, $lang, $subpage){
|
public function sendEmail($addressList, $lang, $subpage){
|
||||||
// Import PHPMailer classes into the global namespace
|
// Import PHPMailer classes into the global namespace
|
||||||
// These must be at the top of your script, not inside a function
|
// These must be at the top of your script, not inside a function
|
||||||
echo $lang;
|
|
||||||
if(!empty($addressList)){
|
if(!empty($addressList)){
|
||||||
foreach($addressList as $addressListItem){
|
foreach($addressList as $addressListItem){
|
||||||
// Instantiation and passing `true` enables exceptions
|
// Instantiation and passing `true` enables exceptions
|
||||||
@@ -179,7 +178,6 @@ class User_model extends CI_Model {
|
|||||||
//$mail->AltBody = strip_tags($answer_message);
|
//$mail->AltBody = strip_tags($answer_message);
|
||||||
|
|
||||||
$mail->send();
|
$mail->send();
|
||||||
echo 'Üzenet elküldve';
|
|
||||||
if($subpage != ''){
|
if($subpage != ''){
|
||||||
header('location:'.base_url().$lang.'/booking-finished/'.$subpage);
|
header('location:'.base_url().$lang.'/booking-finished/'.$subpage);
|
||||||
}
|
}
|
||||||
@@ -187,7 +185,10 @@ class User_model extends CI_Model {
|
|||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
//header('location:'.base_url().'manage-applicants/?email-sent=false&error='.$mail->ErrorInfo);
|
//header('location:'.base_url().'manage-applicants/?email-sent=false&error='.$mail->ErrorInfo);
|
||||||
log_message('error', 'sendEmail FAILED: ' . $mail->ErrorInfo);
|
log_message('error', 'sendEmail FAILED: ' . $mail->ErrorInfo);
|
||||||
echo "Üzenetküldési hiba. Mailer Error: {$mail->ErrorInfo}";
|
// Do NOT echo the mailer error. This runs after the booking is
|
||||||
|
// saved and after the redirect header is queued, so it only leaks
|
||||||
|
// Hungarian internals into a response the guest never reads.
|
||||||
|
// The log_message() above is the record.
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,144 @@
|
|||||||
|
<style>
|
||||||
|
/* Capability grid. Scoped with a ws- prefix so it cannot leak into the other
|
||||||
|
admin forms. Colours follow the existing admin palette: #9e8462 borders and
|
||||||
|
headings, #cacaca body text on the dark background (see .tableClass). */
|
||||||
|
.wsCapabilityRow {
|
||||||
|
max-width: 760px;
|
||||||
|
margin: 0 auto 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wsSectionTitle {
|
||||||
|
font-size: 15px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #9e8462;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wsHint {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #cacaca;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 auto 12px auto;
|
||||||
|
max-width: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wsToolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wsCount {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #cacaca;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* main.css styles every <button> with height:3.5em, letter-spacing:.35em,
|
||||||
|
font-weight:900 and an inset #585858 box-shadow, and forces the colour with
|
||||||
|
!important - so each of those has to be undone explicitly here. */
|
||||||
|
.wsMiniBtn {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
box-shadow: inset 0 0 0 1px #9e8462;
|
||||||
|
color: #9e8462 !important;
|
||||||
|
border-radius: 20px;
|
||||||
|
height: auto;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 5px 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
letter-spacing: normal;
|
||||||
|
text-transform: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wsMiniBtn:hover {
|
||||||
|
background: #9e8462;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wsBox {
|
||||||
|
max-height: 360px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid #9e8462;
|
||||||
|
background: rgba(0, 0, 0, 0.45);
|
||||||
|
padding: 10px 14px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The shared .tableClass centres itself with a 30px margin, which stacks badly
|
||||||
|
when several of them sit inside one scroll box. */
|
||||||
|
.wsTable {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 14px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wsTable th {
|
||||||
|
color: #9e8462;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 1px solid #9e8462;
|
||||||
|
text-align: left;
|
||||||
|
padding: 6px 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wsTable td {
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 1px solid rgba(158, 132, 98, 0.25);
|
||||||
|
padding: 5px 4px;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 1.35;
|
||||||
|
color: #cacaca;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wsTable .wsCheckCell {
|
||||||
|
width: 34px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* modules.css already re-enables the native checkbox for .tableClass tables
|
||||||
|
(main.css hides every checkbox with opacity:0 and draws a fake one via
|
||||||
|
"+ label:before", which cannot work when the label sits in another cell).
|
||||||
|
That rule outranks a plain .wsTable selector, so match its shape rather than
|
||||||
|
fight it - only accent-color and height are added on top. */
|
||||||
|
.wsTable > tbody > tr > td > input[type="checkbox"] {
|
||||||
|
height: 18px;
|
||||||
|
accent-color: #9e8462;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The global label rule carries a 12.6px bottom margin, which was inflating
|
||||||
|
every row to ~54px. */
|
||||||
|
.wsNameCell label {
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1.35;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wsId {
|
||||||
|
color: #7d7d7d;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
.wsBox { max-height: 300px; padding: 8px; }
|
||||||
|
.wsTable td { font-size: 13px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<div class="mainContentContainer" style="top:100px">
|
<div class="mainContentContainer" style="top:100px">
|
||||||
<div class="pageTitle"><?php echo $pageTitle;?></div>
|
<div class="pageTitle"><?php echo $pageTitle;?></div>
|
||||||
<form method="post" action="<?php echo base_url();?>workers/worker-process" enctype="multipart/form-data">
|
<form method="post" action="<?php echo base_url();?>workers/worker-process" enctype="multipart/form-data">
|
||||||
@@ -72,6 +213,76 @@
|
|||||||
<label class="formTitle">ntfy Topic</label>
|
<label class="formTitle">ntfy Topic</label>
|
||||||
<input type="text" class="formInputBox" name="ntfy_topic" value="<?php echo htmlspecialchars(isset($selectedItem->ntfy_topic) ? $selectedItem->ntfy_topic : '', ENT_QUOTES, 'UTF-8');?>">
|
<input type="text" class="formInputBox" name="ntfy_topic" value="<?php echo htmlspecialchars(isset($selectedItem->ntfy_topic) ? $selectedItem->ntfy_topic : '', ENT_QUOTES, 'UTF-8');?>">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="formRow wsCapabilityRow">
|
||||||
|
<div class="wsSectionTitle">Végezhető szolgáltatások</div>
|
||||||
|
<div class="wsHint">
|
||||||
|
Csak a bepipált szolgáltatásokat ajánlja fel a foglalási rendszer ennél a dolgozónál.
|
||||||
|
Ha <strong>mindegyik</strong> be van pipálva, a dolgozó korlátozás nélkül dolgozik, és a
|
||||||
|
később felvett új szolgáltatásokat is automatikusan végezheti. Ha akár egyet is kiveszel,
|
||||||
|
a dolgozó korlátozott lesz, és az ezután létrehozott új szolgáltatásokat külön be kell
|
||||||
|
majd pipálni neki.
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
// No stored rows = unrestricted, so show every box ticked. worker_services
|
||||||
|
// is written back the same way: all ticked saves nothing at all.
|
||||||
|
$wsIds = isset($workerServiceIds) && is_array($workerServiceIds) ? $workerServiceIds : array();
|
||||||
|
$wsUnrestricted = empty($wsIds);
|
||||||
|
$wsHasServices = isset($categoryServices) && is_array($categoryServices) && count($categoryServices) > 0;
|
||||||
|
|
||||||
|
if($wsHasServices){
|
||||||
|
// Group first so each heading can be rendered as its own table.
|
||||||
|
$wsGroups = array();
|
||||||
|
foreach($categoryServices as $svc){
|
||||||
|
$wsGroups[$svc->service_type.' - '.$svc->service_category_no][] = $svc;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="wsToolbar">
|
||||||
|
<span class="wsCount" id="wsCount"></span>
|
||||||
|
<button type="button" class="wsMiniBtn" onclick="wsSetAll(true)">Mindet bepipál</button>
|
||||||
|
<button type="button" class="wsMiniBtn" onclick="wsSetAll(false)">Mindet kivesz</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wsBox">
|
||||||
|
<?php
|
||||||
|
foreach($wsGroups as $wsHeading => $wsRows){
|
||||||
|
?>
|
||||||
|
<table class="tableClass wsTable">
|
||||||
|
<tr>
|
||||||
|
<th colspan="2"><?php echo htmlspecialchars($wsHeading, ENT_QUOTES, 'UTF-8');?></th>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
|
foreach($wsRows as $svc){
|
||||||
|
$checked = $wsUnrestricted || in_array((int)$svc->service_id, $wsIds, TRUE);
|
||||||
|
$svcName = $svc->service_name_hu !== '' ? $svc->service_name_hu : $svc->service_name_no;
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td class="wsCheckCell">
|
||||||
|
<input type="checkbox" class="wsService" id="wsService<?php echo (int)$svc->service_id;?>" name="worker_services[]" value="<?php echo (int)$svc->service_id;?>"<?php echo $checked ? ' checked' : '';?>>
|
||||||
|
</td>
|
||||||
|
<td class="wsNameCell">
|
||||||
|
<label for="wsService<?php echo (int)$svc->service_id;?>">
|
||||||
|
<?php echo htmlspecialchars($svcName, ENT_QUOTES, 'UTF-8');?>
|
||||||
|
<span class="wsId">#<?php echo (int)$svc->service_id;?></span>
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</table>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
?>
|
||||||
|
<div class="wsHint">Ehhez a kategóriához nincs szolgáltatás.</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
<div class="formRow">
|
<div class="formRow">
|
||||||
<input type="submit" class="submitButton" name="updateWorker" value="Módosítás">
|
<input type="submit" class="submitButton" name="updateWorker" value="Módosítás">
|
||||||
<a href="<?php echo base_url();?>workers" class="cancelButton">Mégsem</a>
|
<a href="<?php echo base_url();?>workers" class="cancelButton">Mégsem</a>
|
||||||
@@ -79,3 +290,38 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Keeps the counter honest and powers the two bulk buttons. All ticked is
|
||||||
|
// meaningful state, not just a convenience: the controller stores it as
|
||||||
|
// "no restriction" so the worker keeps getting future services too.
|
||||||
|
function wsRefreshCount(){
|
||||||
|
var boxes = document.querySelectorAll('.wsService');
|
||||||
|
var ticked = document.querySelectorAll('.wsService:checked').length;
|
||||||
|
var el = document.getElementById('wsCount');
|
||||||
|
if(!el){ return; }
|
||||||
|
|
||||||
|
if(boxes.length > 0 && ticked === boxes.length){
|
||||||
|
el.innerHTML = ticked + ' / ' + boxes.length + ' – korlátozás nélkül';
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
el.innerHTML = ticked + ' / ' + boxes.length + ' kiválasztva';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function wsSetAll(state){
|
||||||
|
var boxes = document.querySelectorAll('.wsService');
|
||||||
|
for(var i = 0; i < boxes.length; i++){
|
||||||
|
boxes[i].checked = state;
|
||||||
|
}
|
||||||
|
wsRefreshCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function(){
|
||||||
|
var boxes = document.querySelectorAll('.wsService');
|
||||||
|
for(var i = 0; i < boxes.length; i++){
|
||||||
|
boxes[i].addEventListener('change', wsRefreshCount);
|
||||||
|
}
|
||||||
|
wsRefreshCount();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -32,17 +32,17 @@ $this->load->helper('url');
|
|||||||
<!-- For iPhone 4 Retina display: -->
|
<!-- For iPhone 4 Retina display: -->
|
||||||
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="<?php echo base_url();?>assets/img/favicon/apple-touch-icon-114x114-precomposed.png">
|
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="<?php echo base_url();?>assets/img/favicon/apple-touch-icon-114x114-precomposed.png">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||||
<link rel="stylesheet" href="<?php echo base_url();?>assets/css/barber.css" />
|
<link rel="stylesheet" href="<?php echo asset_ver('assets/css/barber.css');?>" />
|
||||||
<noscript><link rel="stylesheet" href="<?php echo base_url();?>assets/css/noscript.css" /></noscript>
|
<noscript><link rel="stylesheet" href="<?php echo asset_ver('assets/css/noscript.css');?>" /></noscript>
|
||||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
|
||||||
<!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>-->
|
<!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>-->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Open+Sans:ital,wght@0,700;0,800;1,700;1,800&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Open+Sans:ital,wght@0,700;0,800;1,700;1,800&display=swap" rel="stylesheet">
|
||||||
<script src="<?php echo base_url();?>assets/js/jquery/jquery.js"></script>
|
<script src="<?php echo asset_ver('assets/js/jquery/jquery.js');?>"></script>
|
||||||
<link rel="stylesheet" href="<?php echo base_url();?>assets/js/jquery/jquery-ui.css">
|
<link rel="stylesheet" href="<?php echo asset_ver('assets/js/jquery/jquery-ui.css');?>">
|
||||||
<script src="<?php echo base_url();?>assets/js/jquery/jquery-ui.js"></script>
|
<script src="<?php echo asset_ver('assets/js/jquery/jquery-ui.js');?>"></script>
|
||||||
<script src="https://momentjs.com/downloads/moment-with-locales.js"></script>
|
<script src="https://momentjs.com/downloads/moment-with-locales.js"></script>
|
||||||
<!-- Start cookieyes banner --> <script id="cookieyes" type="text/javascript" src="https://cdn-cookieyes.com/client_data/82ccb0b38ad63612e4c6a3fa/script.js"></script> <!-- End cookieyes banner -->
|
<!-- Start cookieyes banner --> <script id="cookieyes" type="text/javascript" src="https://cdn-cookieyes.com/client_data/82ccb0b38ad63612e4c6a3fa/script.js"></script> <!-- End cookieyes banner -->
|
||||||
<link rel="stylesheet" type="text/css" href="<?php echo base_url();?>assets/css/style.css">
|
<link rel="stylesheet" type="text/css" href="<?php echo asset_ver('assets/css/style.css');?>">
|
||||||
<link rel="stylesheet" type="text/css" href="<?php echo base_url();?>assets/css/responsive.css">
|
<link rel="stylesheet" type="text/css" href="<?php echo asset_ver('assets/css/responsive.css');?>">
|
||||||
<script src="<?php echo base_url();?>assets/js/script.js"></script>
|
<script src="<?php echo asset_ver('assets/js/script.js');?>"></script>
|
||||||
</head>
|
</head>
|
||||||
@@ -5,10 +5,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/jquery.dropotron.min.js"></script>
|
<script src="<?php echo asset_ver('assets/js/jquery.dropotron.min.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/browser.min.js"></script>
|
<script src="<?php echo asset_ver('assets/js/browser.min.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/breakpoints.min.js"></script>
|
<script src="<?php echo asset_ver('assets/js/breakpoints.min.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/util.js"></script>
|
<script src="<?php echo asset_ver('assets/js/util.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/studiobave.js"></script>
|
<script src="<?php echo asset_ver('assets/js/studiobave.js');?>"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -31,20 +31,20 @@ $this->load->helper('url');
|
|||||||
<!-- Opera Speed Dial Favicon -->
|
<!-- Opera Speed Dial Favicon -->
|
||||||
<link rel="icon" type="image/png" href="<?php echo base_url();?>assets/img/favicon/speeddial-160px.png" />
|
<link rel="icon" type="image/png" href="<?php echo base_url();?>assets/img/favicon/speeddial-160px.png" />
|
||||||
<!-- For iPhone 4 Retina display: -->
|
<!-- For iPhone 4 Retina display: -->
|
||||||
<script src="<?php echo base_url();?>assets/js/jquery/jquery.js"></script>
|
<script src="<?php echo asset_ver('assets/js/jquery/jquery.js');?>"></script>
|
||||||
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="<?php echo base_url();?>assets/img/favicon/apple-touch-icon-114x114-precomposed.png">
|
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="<?php echo base_url();?>assets/img/favicon/apple-touch-icon-114x114-precomposed.png">
|
||||||
<link rel="stylesheet" href="<?php echo base_url();?>assets/css/beauty.css" />
|
<link rel="stylesheet" href="<?php echo asset_ver('assets/css/beauty.css');?>" />
|
||||||
<noscript><link rel="stylesheet" href="<?php echo base_url();?>assets/css/noscript.css" /></noscript>
|
<noscript><link rel="stylesheet" href="<?php echo asset_ver('assets/css/noscript.css');?>" /></noscript>
|
||||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
|
||||||
<!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>-->
|
<!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>-->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Open+Sans:ital,wght@0,700;0,800;1,700;1,800&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Open+Sans:ital,wght@0,700;0,800;1,700;1,800&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="<?php echo base_url();?>assets/js/jquery/jquery-ui.css">
|
<link rel="stylesheet" href="<?php echo asset_ver('assets/js/jquery/jquery-ui.css');?>">
|
||||||
<script src="<?php echo base_url();?>assets/js/jquery/jquery-ui.js"></script>
|
<script src="<?php echo asset_ver('assets/js/jquery/jquery-ui.js');?>"></script>
|
||||||
<script src="https://momentjs.com/downloads/moment-with-locales.js"></script>
|
<script src="https://momentjs.com/downloads/moment-with-locales.js"></script>
|
||||||
<!-- Start cookieyes banner --> <script id="cookieyes" type="text/javascript" src="https://cdn-cookieyes.com/client_data/82ccb0b38ad63612e4c6a3fa/script.js"></script> <!-- End cookieyes banner -->
|
<!-- Start cookieyes banner --> <script id="cookieyes" type="text/javascript" src="https://cdn-cookieyes.com/client_data/82ccb0b38ad63612e4c6a3fa/script.js"></script> <!-- End cookieyes banner -->
|
||||||
<link rel="stylesheet" type="text/css" href="<?php echo base_url();?>assets/css/style.css">
|
<link rel="stylesheet" type="text/css" href="<?php echo asset_ver('assets/css/style.css');?>">
|
||||||
<link rel="stylesheet" type="text/css" href="<?php echo base_url();?>assets/css/responsive.css">
|
<link rel="stylesheet" type="text/css" href="<?php echo asset_ver('assets/css/responsive.css');?>">
|
||||||
<script src="<?php echo base_url();?>assets/js/script.js"></script>
|
<script src="<?php echo asset_ver('assets/js/script.js');?>"></script>
|
||||||
<!-- Meta Pixel Code -->
|
<!-- Meta Pixel Code -->
|
||||||
<script>
|
<script>
|
||||||
!function(f,b,e,v,n,t,s)
|
!function(f,b,e,v,n,t,s)
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/jquery.dropotron.min.js"></script>
|
<script src="<?php echo asset_ver('assets/js/jquery.dropotron.min.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/browser.min.js"></script>
|
<script src="<?php echo asset_ver('assets/js/browser.min.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/breakpoints.min.js"></script>
|
<script src="<?php echo asset_ver('assets/js/breakpoints.min.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/util.js"></script>
|
<script src="<?php echo asset_ver('assets/js/util.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/studiobave.js"></script>
|
<script src="<?php echo asset_ver('assets/js/studiobave.js');?>"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -31,21 +31,21 @@ $this->load->helper('url');
|
|||||||
<!-- For iPhone 4 Retina display: -->
|
<!-- For iPhone 4 Retina display: -->
|
||||||
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="<?php echo base_url();?>assets/img/favicon/apple-touch-icon-114x114-precomposed.png">
|
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="<?php echo base_url();?>assets/img/favicon/apple-touch-icon-114x114-precomposed.png">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||||
<link rel="stylesheet" href="<?php echo base_url();?>assets/css/main.css" />
|
<link rel="stylesheet" href="<?php echo asset_ver('assets/css/main.css');?>" />
|
||||||
<noscript><link rel="stylesheet" href="<?php echo base_url();?>assets/css/noscript.css" /></noscript>
|
<noscript><link rel="stylesheet" href="<?php echo asset_ver('assets/css/noscript.css');?>" /></noscript>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Open+Sans:ital,wght@0,700;0,800;1,700;1,800&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Open+Sans:ital,wght@0,700;0,800;1,700;1,800&display=swap" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
|
||||||
<script src="https://momentjs.com/downloads/moment-with-locales.js"></script>
|
<script src="https://momentjs.com/downloads/moment-with-locales.js"></script>
|
||||||
<script src="<?php echo base_url();?>assets/js/jquery.flexdatalist.min.js"></script>
|
<script src="<?php echo asset_ver('assets/js/jquery.flexdatalist.min.js');?>"></script>
|
||||||
<!-- Start cookieyes banner --> <script id="cookieyes" type="text/javascript" src="https://cdn-cookieyes.com/client_data/82ccb0b38ad63612e4c6a3fa/script.js"></script> <!-- End cookieyes banner -->
|
<!-- Start cookieyes banner --> <script id="cookieyes" type="text/javascript" src="https://cdn-cookieyes.com/client_data/82ccb0b38ad63612e4c6a3fa/script.js"></script> <!-- End cookieyes banner -->
|
||||||
<link href="<?php echo base_url();?>assets/js/jquery.flexdatalist.min.css" rel="stylesheet" type="text/css">
|
<link href="<?php echo asset_ver('assets/js/jquery.flexdatalist.min.css');?>" rel="stylesheet" type="text/css">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Open+Sans:ital,wght@0,700;0,800;1,700;1,800&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Open+Sans:ital,wght@0,700;0,800;1,700;1,800&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" type="text/css" href="<?php echo base_url();?>assets/css/style.css">
|
<link rel="stylesheet" type="text/css" href="<?php echo asset_ver('assets/css/style.css');?>">
|
||||||
<link rel="stylesheet" type="text/css" href="<?php echo base_url();?>assets/css/responsive.css">
|
<link rel="stylesheet" type="text/css" href="<?php echo asset_ver('assets/css/responsive.css');?>">
|
||||||
<link rel="stylesheet" type="text/css" href="<?php echo base_url();?>assets/css/modules.css">
|
<link rel="stylesheet" type="text/css" href="<?php echo asset_ver('assets/css/modules.css');?>">
|
||||||
<link rel="stylesheet" type="text/css" href="<?php echo base_url();?>assets/css/calendar.css">
|
<link rel="stylesheet" type="text/css" href="<?php echo asset_ver('assets/css/calendar.css');?>">
|
||||||
<script src="<?php echo base_url();?>assets/js/script.js"></script>
|
<script src="<?php echo asset_ver('assets/js/script.js');?>"></script>
|
||||||
<!-- Meta Pixel Code -->
|
<!-- Meta Pixel Code -->
|
||||||
<script>
|
<script>
|
||||||
!function(f,b,e,v,n,t,s)
|
!function(f,b,e,v,n,t,s)
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/jquery.min.js"></script>
|
<script src="<?php echo asset_ver('assets/js/jquery.min.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/jquery.dropotron.min.js"></script>
|
<script src="<?php echo asset_ver('assets/js/jquery.dropotron.min.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/browser.min.js"></script>
|
<script src="<?php echo asset_ver('assets/js/browser.min.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/breakpoints.min.js"></script>
|
<script src="<?php echo asset_ver('assets/js/breakpoints.min.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/util.js"></script>
|
<script src="<?php echo asset_ver('assets/js/util.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/main.js"></script>
|
<script src="<?php echo asset_ver('assets/js/main.js');?>"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -34,17 +34,17 @@ $this->load->helper('url');
|
|||||||
<!-- Opera Speed Dial Favicon -->
|
<!-- Opera Speed Dial Favicon -->
|
||||||
<link rel="icon" type="image/png" href="<?php echo base_url();?>assets/img/favicon/speeddial-160px.png" />
|
<link rel="icon" type="image/png" href="<?php echo base_url();?>assets/img/favicon/speeddial-160px.png" />
|
||||||
<!-- For iPhone 4 Retina display: -->
|
<!-- For iPhone 4 Retina display: -->
|
||||||
<script src="<?php echo base_url();?>assets/js/jquery/jquery.js"></script>
|
<script src="<?php echo asset_ver('assets/js/jquery/jquery.js');?>"></script>
|
||||||
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="<?php echo base_url();?>assets/img/favicon/apple-touch-icon-114x114-precomposed.png">
|
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="<?php echo base_url();?>assets/img/favicon/apple-touch-icon-114x114-precomposed.png">
|
||||||
<link rel="stylesheet" href="<?php echo base_url();?>assets/css/<?php echo $vertical['css'];?>" />
|
<link rel="stylesheet" href="<?php echo asset_ver('assets/css/'.$vertical['css']);?>" />
|
||||||
<noscript><link rel="stylesheet" href="<?php echo base_url();?>assets/css/noscript.css" /></noscript>
|
<noscript><link rel="stylesheet" href="<?php echo asset_ver('assets/css/noscript.css');?>" /></noscript>
|
||||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Open+Sans:ital,wght@0,700;0,800;1,700;1,800&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Open+Sans:ital,wght@0,700;0,800;1,700;1,800&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="<?php echo base_url();?>assets/js/jquery/jquery-ui.css">
|
<link rel="stylesheet" href="<?php echo asset_ver('assets/js/jquery/jquery-ui.css');?>">
|
||||||
<script src="<?php echo base_url();?>assets/js/jquery/jquery-ui.js"></script>
|
<script src="<?php echo asset_ver('assets/js/jquery/jquery-ui.js');?>"></script>
|
||||||
<script src="https://momentjs.com/downloads/moment-with-locales.js"></script>
|
<script src="https://momentjs.com/downloads/moment-with-locales.js"></script>
|
||||||
<!-- Start cookieyes banner --> <script id="cookieyes" type="text/javascript" src="https://cdn-cookieyes.com/client_data/82ccb0b38ad63612e4c6a3fa/script.js"></script> <!-- End cookieyes banner -->
|
<!-- Start cookieyes banner --> <script id="cookieyes" type="text/javascript" src="https://cdn-cookieyes.com/client_data/82ccb0b38ad63612e4c6a3fa/script.js"></script> <!-- End cookieyes banner -->
|
||||||
<link rel="stylesheet" type="text/css" href="<?php echo base_url();?>assets/css/style.css">
|
<link rel="stylesheet" type="text/css" href="<?php echo asset_ver('assets/css/style.css');?>">
|
||||||
<link rel="stylesheet" type="text/css" href="<?php echo base_url();?>assets/css/responsive.css">
|
<link rel="stylesheet" type="text/css" href="<?php echo asset_ver('assets/css/responsive.css');?>">
|
||||||
<script src="<?php echo base_url();?>assets/js/script.js"></script>
|
<script src="<?php echo asset_ver('assets/js/script.js');?>"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/jquery.dropotron.min.js"></script>
|
<script src="<?php echo asset_ver('assets/js/jquery.dropotron.min.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/browser.min.js"></script>
|
<script src="<?php echo asset_ver('assets/js/browser.min.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/breakpoints.min.js"></script>
|
<script src="<?php echo asset_ver('assets/js/breakpoints.min.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/util.js"></script>
|
<script src="<?php echo asset_ver('assets/js/util.js');?>"></script>
|
||||||
<script src="<?php echo SITEURL;?>assets/js/studiobave.js"></script>
|
<script src="<?php echo asset_ver('assets/js/studiobave.js');?>"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -49,8 +49,8 @@
|
|||||||
<p>
|
<p>
|
||||||
With nearly 10 years of experience in the beauty industry, including many years
|
With nearly 10 years of experience in the beauty industry, including many years
|
||||||
of international experience, I thought it was time to open my own studio here in
|
of international experience, I thought it was time to open my own studio here in
|
||||||
Trondheim at Innheredsveien 92, where guests can park free of charge during
|
Trondheim at Innheredsveien 92. We have two private parking spaces that our
|
||||||
the treatments.
|
guests can use free of charge during their treatment, subject to availability.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
It is an extra pleasure for me that I managed to find a traditional place where
|
It is an extra pleasure for me that I managed to find a traditional place where
|
||||||
@@ -100,6 +100,21 @@
|
|||||||
<img src="<?php echo SITEURL;?>assets/img/barber/evelin/4.jpg">
|
<img src="<?php echo SITEURL;?>assets/img/barber/evelin/4.jpg">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="dolgozo-nev">
|
||||||
|
<hr>
|
||||||
|
<img src="<?php echo SITEURL;?>assets/img/barber/amarildo/amarildo.jpg" class="dolgozo-kep">
|
||||||
|
<h4>AMARILDO Champimpi <span>- Barber</span></h4>
|
||||||
|
</div>
|
||||||
|
<div class="bemutatkozas-dolgozo no-gallery">
|
||||||
|
<div class="bem-dolgozo-bal">
|
||||||
|
<p>Amarildo here, originally from Albania. I have more than six years of experience as a barber, and throughout my career I have worked in both Greece and England. I have been living in Norway for the past three years, and I am now happy to join the Studio Beve team.</p>
|
||||||
|
<p>I specialise in both modern and classic haircuts, as well as skin fades and taper fades. Precision and attention to detail are very important to me, and I always strive to achieve the best possible result for every client.</p>
|
||||||
|
<p>Since I started working as a barber, my profession has become a true passion of mine. I continuously develop my skills, learn new techniques, and keep myself up to date with the latest trends and styles.</p>
|
||||||
|
<p>For me, barbering is not simply about giving someone a haircut. It is important to create a style that truly suits each individual and to make sure every client leaves my chair feeling confident and satisfied.</p>
|
||||||
|
<p>I am excited to bring the experience I have gained over the years, my dedication to the profession, and my creativity to Studio Beve, and I look forward to welcoming you to my chair soon.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -260,7 +275,7 @@
|
|||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo SITEURL;?>assets/img/barber/ollo.png">
|
<img src="<?php echo SITEURL;?>assets/img/barber/ollo.png">
|
||||||
<h2 class="nyitva">Working Hours</h2>
|
<h2 class="nyitva">Working Hours</h2>
|
||||||
<h4>Free customer parking during treatments</h4>
|
<h4>2 free customer parking spaces – subject to availability</h4>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
<div class="nyitvatartas">
|
<div class="nyitvatartas">
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
Hiszem, hogy a szépség, a divat és a trend nem nemhez kötött, így nálunk lehetőség van arra, hogy mindenki egy helyen szépülhessen nemtől függetlenül. A Barber, Beauty és Masszázs részlegeink között pedig megtalálhatjátok a számotokra megfelelő szolgáltatásokat.
|
Hiszem, hogy a szépség, a divat és a trend nem nemhez kötött, így nálunk lehetőség van arra, hogy mindenki egy helyen szépülhessen nemtől függetlenül. A Barber, Beauty és Masszázs részlegeink között pedig megtalálhatjátok a számotokra megfelelő szolgáltatásokat.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Közel 10 éves szépségipari, és közte több éves nemzetközi tapasztalattal elérkezettnek láttam az időt, hogy megnyissam saját stúdiómat itt Trondheimben, az Innheredsveien 92 szám alatt, ahol a vendégeknek lehetősége van ingyenes parkolásra is a kezelések időtartama alatt.
|
Közel 10 éves szépségipari, és közte több éves nemzetközi tapasztalattal elérkezettnek láttam az időt, hogy megnyissam saját stúdiómat itt Trondheimben, az Innheredsveien 92 szám alatt. Két privát parkolóhellyel rendelkezünk, amelyeket vendégeink a kezelés ideje alatt ingyenesen használhatnak, amennyiben van szabad hely.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Extra öröm számomra, hogy egy olyan tradicionális helyet sikerült találnom, ahol az 50-es években már szépségszalon működött, ennek köszönhetően a hagyományt, modern környezetben lehetőségem van tovább vinni.
|
Extra öröm számomra, hogy egy olyan tradicionális helyet sikerült találnom, ahol az 50-es években már szépségszalon működött, ennek köszönhetően a hagyományt, modern környezetben lehetőségem van tovább vinni.
|
||||||
@@ -76,6 +76,21 @@
|
|||||||
<img src="<?php echo SITEURL;?>assets/img/barber/evelin/4.jpg">
|
<img src="<?php echo SITEURL;?>assets/img/barber/evelin/4.jpg">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="dolgozo-nev">
|
||||||
|
<hr>
|
||||||
|
<img src="<?php echo SITEURL;?>assets/img/barber/amarildo/amarildo.jpg" class="dolgozo-kep">
|
||||||
|
<h4>AMARILDO Champimpi <span>- Barber</span></h4>
|
||||||
|
</div>
|
||||||
|
<div class="bemutatkozas-dolgozo no-gallery">
|
||||||
|
<div class="bem-dolgozo-bal">
|
||||||
|
<p>Amarildo vagyok, és Albániából érkeztem. Több mint hatéves tapasztalattal rendelkezem barberként, pályafutásom során Görögországban és Angliában is dolgoztam. Három éve élek Norvégiában, és most örömmel csatlakozom a Studio Beve csapatához.</p>
|
||||||
|
<p>Modern és klasszikus hajvágásokra, valamint skin fade és taper fade technikákra specializálódtam. A munkámban fontos számomra a precizitás és a részletekre való odafigyelés, és mindig arra törekszem, hogy minden vendégem számára a lehető legjobb eredményt érjem el.</p>
|
||||||
|
<p>Amióta barberként dolgozom, a szakmám valódi szenvedélyemmé vált. Folyamatosan fejlesztem a tudásomat, új technikákat tanulok, és igyekszem naprakész maradni a legújabb trendekkel és stílusokkal.</p>
|
||||||
|
<p>Számomra a barbering nem egyszerűen egy hajvágás elkészítéséről szól. Fontos, hogy olyan stílust alakítsak ki, amely valóban illik az adott vendéghez, és hogy mindenki magabiztosan és elégedetten távozzon a székemből.</p>
|
||||||
|
<p>Örömmel hozom a Studio Bevébe az elmúlt években megszerzett tapasztalataimat, a szakmám iránti elkötelezettségemet és a kreativitásomat, és várom, hogy hamarosan téged is üdvözölhesselek a székemben.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -237,7 +252,7 @@
|
|||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo SITEURL;?>assets/img/ollo.png">
|
<img src="<?php echo SITEURL;?>assets/img/ollo.png">
|
||||||
<h2 class="nyitva">Nyitvatartási idő</h2>
|
<h2 class="nyitva">Nyitvatartási idő</h2>
|
||||||
<h4>Ingyenes parkolás a szolgáltatások ideje alatt</h4>
|
<h4>2 ingyenes vendégparkoló – szabad hely esetén</h4>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
<div class="nyitvatartas">
|
<div class="nyitvatartas">
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
Jeg mener at skjønnhet, mote og trend ikke er kjønnsspesifikke, og hos oss har derfor alle mulighet til å bli vakrere på ett sted, uavhengig av kjønn. Hos Studio Beve Barber, Studio Beve Beauty og Studio Beve Massasje kan dere finne de tjenestene som er riktige for dere.
|
Jeg mener at skjønnhet, mote og trend ikke er kjønnsspesifikke, og hos oss har derfor alle mulighet til å bli vakrere på ett sted, uavhengig av kjønn. Hos Studio Beve Barber, Studio Beve Beauty og Studio Beve Massasje kan dere finne de tjenestene som er riktige for dere.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Med nesten 10 års erfaring fra skjønnhetsindustrien, inkludert mange års internasjonal erfaring, tenkte jeg det var på tide å åpne mitt eget studio her i Trondheim i Innheredsveien 92, hvor gjestene har mulighet til å parkere gratis under behandlingene.
|
Med nesten 10 års erfaring fra skjønnhetsindustrien, inkludert mange års internasjonal erfaring, tenkte jeg det var på tide å åpne mitt eget studio her i Trondheim i Innheredsveien 92. Vi har to private parkeringsplasser som våre gjester kan benytte gratis under behandlingen, dersom det er ledig kapasitet.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Det er en ekstra glede for meg at jeg klarte å finne et tradisjonelt sted hvor det allerede var en skjønnhetssalong på 50-tallet. Derfor har jeg nå muligheten til å videreføre denne tradisjonen i et moderne miljø.
|
Det er en ekstra glede for meg at jeg klarte å finne et tradisjonelt sted hvor det allerede var en skjønnhetssalong på 50-tallet. Derfor har jeg nå muligheten til å videreføre denne tradisjonen i et moderne miljø.
|
||||||
@@ -77,6 +77,21 @@
|
|||||||
<img src="<?php echo SITEURL;?>assets/img/barber/evelin/4.jpg">
|
<img src="<?php echo SITEURL;?>assets/img/barber/evelin/4.jpg">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="dolgozo-nev">
|
||||||
|
<hr>
|
||||||
|
<img src="<?php echo SITEURL;?>assets/img/barber/amarildo/amarildo.jpg" class="dolgozo-kep">
|
||||||
|
<h4>AMARILDO Champimpi <span>- Barber</span></h4>
|
||||||
|
</div>
|
||||||
|
<div class="bemutatkozas-dolgozo no-gallery">
|
||||||
|
<div class="bem-dolgozo-bal">
|
||||||
|
<p>Jeg heter Amarildo og kommer opprinnelig fra Albania. Jeg har mer enn seks års erfaring som barber, og gjennom karrieren min har jeg jobbet både i Hellas og England. Jeg har bodd i Norge de siste tre årene, og nå er jeg glad for å bli en del av teamet hos Studio Beve.</p>
|
||||||
|
<p>Jeg spesialiserer meg på både moderne og klassiske hårklipper, samt skin fade og taper fade. Presisjon og sans for detaljer er svært viktig for meg, og jeg streber alltid etter å oppnå best mulig resultat for hver enkelt kunde.</p>
|
||||||
|
<p>Siden jeg begynte å jobbe som barber, har yrket blitt en ekte lidenskap for meg. Jeg utvikler meg kontinuerlig, lærer nye teknikker og holder meg oppdatert på de nyeste trendene og stilene.</p>
|
||||||
|
<p>For meg handler barberfaget ikke bare om å klippe håret. Det er viktig å skape en stil som virkelig passer den enkelte, og sørge for at hver kunde forlater stolen med en følelse av selvtillit og tilfredshet.</p>
|
||||||
|
<p>Jeg gleder meg til å ta med meg erfaringen jeg har opparbeidet gjennom årene, engasjementet mitt for faget og kreativiteten min til Studio Beve, og jeg ser frem til å ønske deg velkommen i stolen min.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -236,7 +251,7 @@
|
|||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo SITEURL;?>assets/img/ollo.png">
|
<img src="<?php echo SITEURL;?>assets/img/ollo.png">
|
||||||
<h2 class="nyitva">Åpningstider</h2>
|
<h2 class="nyitva">Åpningstider</h2>
|
||||||
<h4>Gratis kundeprarking under behandlingen</h4>
|
<h4>2 gratis kundeparkeringsplasser – ved ledig kapasitet</h4>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
<div class="nyitvatartas">
|
<div class="nyitvatartas">
|
||||||
|
|||||||
@@ -47,8 +47,8 @@
|
|||||||
<p>
|
<p>
|
||||||
With nearly 10 years of experience in the beauty industry, including many years
|
With nearly 10 years of experience in the beauty industry, including many years
|
||||||
of international experience, I thought it was time to open my own studio here in
|
of international experience, I thought it was time to open my own studio here in
|
||||||
Trondheim at Innheredsveien 92, where guests can park free of charge during
|
Trondheim at Innheredsveien 92. We have two private parking spaces that our
|
||||||
the treatments.
|
guests can use free of charge during their treatment, subject to availability.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
It is an extra pleasure for me that I managed to find a traditional place where
|
It is an extra pleasure for me that I managed to find a traditional place where
|
||||||
@@ -266,7 +266,7 @@
|
|||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo SITEURL;?>assets/img/beauty/ollo_rose.png">
|
<img src="<?php echo SITEURL;?>assets/img/beauty/ollo_rose.png">
|
||||||
<h2 class="nyitva">Working Hours</h2>
|
<h2 class="nyitva">Working Hours</h2>
|
||||||
<h4>Free customer parking during treatments</h4>
|
<h4>2 free customer parking spaces – subject to availability</h4>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
<div class="nyitvatartas">
|
<div class="nyitvatartas">
|
||||||
|
|||||||
@@ -45,8 +45,9 @@
|
|||||||
<p>
|
<p>
|
||||||
Közel 10 éves szépségipari, és közte több éves nemzetközi tapasztalattal
|
Közel 10 éves szépségipari, és közte több éves nemzetközi tapasztalattal
|
||||||
elérkezettnek láttam az időt, hogy megnyissam saját stúdiómat itt
|
elérkezettnek láttam az időt, hogy megnyissam saját stúdiómat itt
|
||||||
Trondheimben, az Innheredsveien 92 szám alatt, ahol a vendégeknek lehetősége
|
Trondheimben, az Innheredsveien 92 szám alatt. Két privát parkolóhellyel
|
||||||
van ingyenes parkolásra is a kezelések időtartama alatt.
|
rendelkezünk, amelyeket vendégeink a kezelés ideje alatt ingyenesen
|
||||||
|
használhatnak, amennyiben van szabad hely.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Extra öröm számomra, hogy egy olyan tradicionális helyet sikerült találnom,
|
Extra öröm számomra, hogy egy olyan tradicionális helyet sikerült találnom,
|
||||||
@@ -286,7 +287,7 @@
|
|||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo SITEURL;?>assets/img/beauty/ollo_rose.png">
|
<img src="<?php echo SITEURL;?>assets/img/beauty/ollo_rose.png">
|
||||||
<h2 class="nyitva">Nyitvatartási idő</h2>
|
<h2 class="nyitva">Nyitvatartási idő</h2>
|
||||||
<h4>Ingyenes parkolás a szolgáltatások ideje alatt</h4>
|
<h4>2 ingyenes vendégparkoló – szabad hely esetén</h4>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
<div class="nyitvatartas">
|
<div class="nyitvatartas">
|
||||||
|
|||||||
@@ -46,8 +46,8 @@
|
|||||||
<p>
|
<p>
|
||||||
Med nesten 10 års erfaring fra skjønnhetsindustrien, inkludert mange års
|
Med nesten 10 års erfaring fra skjønnhetsindustrien, inkludert mange års
|
||||||
internasjonal erfaring, tenkte jeg det var på tide å åpne mitt eget studio her i
|
internasjonal erfaring, tenkte jeg det var på tide å åpne mitt eget studio her i
|
||||||
Trondheim i Innheredsveien 92, hvor gjestene har mulighet til å parkere gratis
|
Trondheim i Innheredsveien 92. Vi har to private parkeringsplasser som våre
|
||||||
under behandlingene.
|
gjester kan benytte gratis under behandlingen, dersom det er ledig kapasitet.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Det er en ekstra glede for meg at jeg klarte å finne et tradisjonelt sted hvor det
|
Det er en ekstra glede for meg at jeg klarte å finne et tradisjonelt sted hvor det
|
||||||
@@ -286,7 +286,7 @@
|
|||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo SITEURL;?>assets/img/beauty/ollo_rose.png">
|
<img src="<?php echo SITEURL;?>assets/img/beauty/ollo_rose.png">
|
||||||
<h2 class="nyitva">Åpningstider</h2>
|
<h2 class="nyitva">Åpningstider</h2>
|
||||||
<h4>Gratis kunderparking under behandlingen</h4>
|
<h4>2 gratis kundeparkeringsplasser – ved ledig kapasitet</h4>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
<div class="nyitvatartas">
|
<div class="nyitvatartas">
|
||||||
|
|||||||
@@ -7,7 +7,10 @@
|
|||||||
'after_hours' => 'The treatment would not finish before closing time. Please choose an earlier time.',
|
'after_hours' => 'The treatment would not finish before closing time. Please choose an earlier time.',
|
||||||
'too_far' => 'Bookings can only be made up to 3 months in advance.',
|
'too_far' => 'Bookings can only be made up to 3 months in advance.',
|
||||||
'category_mismatch' => 'The selected staff member does not perform the chosen services. Please start again.',
|
'category_mismatch' => 'The selected staff member does not perform the chosen services. Please start again.',
|
||||||
|
'service_not_offered' => 'The selected staff member does not perform all of the chosen treatments. Please pick another staff member or change the treatments.',
|
||||||
'single_service_only' => 'Only one treatment can be booked at a time. Please select just one.',
|
'single_service_only' => 'Only one treatment can be booked at a time. Please select just one.',
|
||||||
|
'invalid_email' => 'That e-mail address does not look valid. Please check it - your confirmation and the link to manage your booking are sent there.',
|
||||||
|
'cutoff_passed' => 'Your booking is within 24 hours and can no longer be changed or cancelled. Please contact the salon.',
|
||||||
'no_service' => 'No service was selected. Please choose at least one service.',
|
'no_service' => 'No service was selected. Please choose at least one service.',
|
||||||
'default' => 'Something went wrong with your booking. Please try again.',
|
'default' => 'Something went wrong with your booking. Please try again.',
|
||||||
);
|
);
|
||||||
@@ -17,12 +20,24 @@
|
|||||||
: $bookingErrorMessages['default'];
|
: $bookingErrorMessages['default'];
|
||||||
|
|
||||||
$backSubpage = isset($subpage) && $subpage != '' ? $subpage : 'barber';
|
$backSubpage = isset($subpage) && $subpage != '' ? $subpage : 'barber';
|
||||||
$backUrl = SITEURL.'en/booking/'.$backSubpage;
|
|
||||||
|
// _booking_error() passes a back URL when the error came from the
|
||||||
|
// manage-booking flow; that guest must return to their own booking page
|
||||||
|
// rather than being dropped into an empty new-booking form.
|
||||||
|
$isManageError = isset($bookingBackUrl) && $bookingBackUrl !== '';
|
||||||
|
$backUrl = $isManageError ? $bookingBackUrl : SITEURL.'en/booking/'.$backSubpage;
|
||||||
|
$backLabel = $isManageError ? 'Back to your booking' : 'Back to booking';
|
||||||
|
|
||||||
|
if(isset($bookingErrorCode) && $bookingErrorCode === 'slot_taken'){
|
||||||
|
$errorTitle = 'This time is no longer available';
|
||||||
|
} else {
|
||||||
|
$errorTitle = $isManageError ? 'Your booking could not be changed' : 'Your booking could not be completed';
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
<div class="bookingContainer" style="min-height:500px;">
|
<div class="bookingContainer" style="min-height:500px;">
|
||||||
<div class="bookingThankYouTitle">This time is no longer available</div>
|
<div class="bookingThankYouTitle"><?php echo $errorTitle;?></div>
|
||||||
<div class="bookingThankYouMessageTitle"><?php echo $shownError;?></div>
|
<div class="bookingThankYouMessageTitle"><?php echo $shownError;?></div>
|
||||||
<div style="text-align:center; margin-top:30px;">
|
<div style="text-align:center; margin-top:30px;">
|
||||||
<a href="<?php echo $backUrl;?>" style="display:inline-block; padding:12px 28px; background:#000; color:#fff; text-decoration:none; border-radius:5px; font-size:16px;">Back to booking</a>
|
<a href="<?php echo $backUrl;?>" style="display:inline-block; padding:12px 28px; background:#000; color:#fff; text-decoration:none; border-radius:5px; font-size:16px;"><?php echo $backLabel;?></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,10 @@
|
|||||||
'after_hours' => 'A kezelés nem érne véget zárásig. Kérjük, válasszon korábbi időpontot.',
|
'after_hours' => 'A kezelés nem érne véget zárásig. Kérjük, válasszon korábbi időpontot.',
|
||||||
'too_far' => 'Foglalás legfeljebb 3 hónappal előre adható le.',
|
'too_far' => 'Foglalás legfeljebb 3 hónappal előre adható le.',
|
||||||
'category_mismatch' => 'A kiválasztott kolléga nem végzi a kiválasztott szolgáltatásokat. Kérjük, válasszon újra.',
|
'category_mismatch' => 'A kiválasztott kolléga nem végzi a kiválasztott szolgáltatásokat. Kérjük, válasszon újra.',
|
||||||
|
'service_not_offered' => 'A kiválasztott kolléga nem végzi el az összes kiválasztott szolgáltatást. Kérjük, válasszon másik kollégát, vagy módosítsa a szolgáltatásokat.',
|
||||||
'single_service_only' => 'Egyszerre csak egy kezelés foglalható. Kérjük, csak egyet válasszon.',
|
'single_service_only' => 'Egyszerre csak egy kezelés foglalható. Kérjük, csak egyet válasszon.',
|
||||||
|
'invalid_email' => 'Az e-mail cím nem tűnik érvényesnek. Kérjük, ellenőrizze - a visszaigazolást és a foglalás kezelésére szolgáló linket erre a címre küldjük.',
|
||||||
|
'cutoff_passed' => 'A foglalás 24 órán belül van, ezért már nem módosítható és nem törölhető. Kérjük, vedd fel a kapcsolatot a szalonnal.',
|
||||||
'no_service' => 'Nem választott ki szolgáltatást. Kérjük, válasszon legalább egyet.',
|
'no_service' => 'Nem választott ki szolgáltatást. Kérjük, válasszon legalább egyet.',
|
||||||
'default' => 'Hiba történt a foglalás során. Kérjük, próbálja újra.',
|
'default' => 'Hiba történt a foglalás során. Kérjük, próbálja újra.',
|
||||||
);
|
);
|
||||||
@@ -17,12 +20,24 @@
|
|||||||
: $bookingErrorMessages['default'];
|
: $bookingErrorMessages['default'];
|
||||||
|
|
||||||
$backSubpage = isset($subpage) && $subpage != '' ? $subpage : 'barber';
|
$backSubpage = isset($subpage) && $subpage != '' ? $subpage : 'barber';
|
||||||
$backUrl = SITEURL.'hu/booking/'.$backSubpage;
|
|
||||||
|
// _booking_error() passes a back URL when the error came from the
|
||||||
|
// manage-booking flow; that guest must return to their own booking page
|
||||||
|
// rather than being dropped into an empty new-booking form.
|
||||||
|
$isManageError = isset($bookingBackUrl) && $bookingBackUrl !== '';
|
||||||
|
$backUrl = $isManageError ? $bookingBackUrl : SITEURL.'hu/booking/'.$backSubpage;
|
||||||
|
$backLabel = $isManageError ? 'Vissza a foglalásodhoz' : 'Vissza a foglaláshoz';
|
||||||
|
|
||||||
|
if(isset($bookingErrorCode) && $bookingErrorCode === 'slot_taken'){
|
||||||
|
$errorTitle = 'Ez az időpont már nem foglalható';
|
||||||
|
} else {
|
||||||
|
$errorTitle = $isManageError ? 'A foglalást nem sikerült módosítani' : 'A foglalást nem sikerült véglegesíteni';
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
<div class="bookingContainer" style="min-height:500px;">
|
<div class="bookingContainer" style="min-height:500px;">
|
||||||
<div class="bookingThankYouTitle">Ez az időpont már nem foglalható</div>
|
<div class="bookingThankYouTitle"><?php echo $errorTitle;?></div>
|
||||||
<div class="bookingThankYouMessageTitle"><?php echo $shownError;?></div>
|
<div class="bookingThankYouMessageTitle"><?php echo $shownError;?></div>
|
||||||
<div style="text-align:center; margin-top:30px;">
|
<div style="text-align:center; margin-top:30px;">
|
||||||
<a href="<?php echo $backUrl;?>" style="display:inline-block; padding:12px 28px; background:#000; color:#fff; text-decoration:none; border-radius:5px; font-size:16px;">Vissza a foglaláshoz</a>
|
<a href="<?php echo $backUrl;?>" style="display:inline-block; padding:12px 28px; background:#000; color:#fff; text-decoration:none; border-radius:5px; font-size:16px;"><?php echo $backLabel;?></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,10 @@
|
|||||||
'after_hours' => 'Behandlingen rekker ikke å bli ferdig innen stengetid. Vennligst velg et tidligere tidspunkt.',
|
'after_hours' => 'Behandlingen rekker ikke å bli ferdig innen stengetid. Vennligst velg et tidligere tidspunkt.',
|
||||||
'too_far' => 'Du kan kun bestille time inntil 3 måneder frem i tid.',
|
'too_far' => 'Du kan kun bestille time inntil 3 måneder frem i tid.',
|
||||||
'category_mismatch' => 'Den valgte medarbeideren utfører ikke de valgte tjenestene. Vennligst velg på nytt.',
|
'category_mismatch' => 'Den valgte medarbeideren utfører ikke de valgte tjenestene. Vennligst velg på nytt.',
|
||||||
|
'service_not_offered' => 'Den valgte medarbeideren utfører ikke alle de valgte behandlingene. Vennligst velg en annen medarbeider eller endre behandlingene.',
|
||||||
'single_service_only' => 'Du kan kun bestille én behandling om gangen. Vennligst velg kun én.',
|
'single_service_only' => 'Du kan kun bestille én behandling om gangen. Vennligst velg kun én.',
|
||||||
|
'invalid_email' => 'E-postadressen ser ikke ut til å være gyldig. Vennligst kontroller den - bekreftelsen og lenken for å endre bestillingen sendes dit.',
|
||||||
|
'cutoff_passed' => 'Bestillingen er innen 24 timer og kan ikke lenger endres eller avbestilles. Vennligst ta kontakt med salongen.',
|
||||||
'no_service' => 'Du har ikke valgt noen tjeneste. Vennligst velg minst én tjeneste.',
|
'no_service' => 'Du har ikke valgt noen tjeneste. Vennligst velg minst én tjeneste.',
|
||||||
'default' => 'Noe gikk galt med bestillingen. Vennligst prøv igjen.',
|
'default' => 'Noe gikk galt med bestillingen. Vennligst prøv igjen.',
|
||||||
);
|
);
|
||||||
@@ -17,12 +20,24 @@
|
|||||||
: $bookingErrorMessages['default'];
|
: $bookingErrorMessages['default'];
|
||||||
|
|
||||||
$backSubpage = isset($subpage) && $subpage != '' ? $subpage : 'barber';
|
$backSubpage = isset($subpage) && $subpage != '' ? $subpage : 'barber';
|
||||||
$backUrl = SITEURL.'no/booking/'.$backSubpage;
|
|
||||||
|
// _booking_error() passes a back URL when the error came from the
|
||||||
|
// manage-booking flow; that guest must return to their own booking page
|
||||||
|
// rather than being dropped into an empty new-booking form.
|
||||||
|
$isManageError = isset($bookingBackUrl) && $bookingBackUrl !== '';
|
||||||
|
$backUrl = $isManageError ? $bookingBackUrl : SITEURL.'no/booking/'.$backSubpage;
|
||||||
|
$backLabel = $isManageError ? 'Tilbake til bestillingen din' : 'Tilbake til bestilling';
|
||||||
|
|
||||||
|
if(isset($bookingErrorCode) && $bookingErrorCode === 'slot_taken'){
|
||||||
|
$errorTitle = 'Tidspunktet er ikke lenger tilgjengelig';
|
||||||
|
} else {
|
||||||
|
$errorTitle = $isManageError ? 'Bestillingen kunne ikke endres' : 'Bestillingen kunne ikke fullføres';
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
<div class="bookingContainer" style="min-height:500px;">
|
<div class="bookingContainer" style="min-height:500px;">
|
||||||
<div class="bookingThankYouTitle">Tidspunktet er ikke lenger tilgjengelig</div>
|
<div class="bookingThankYouTitle"><?php echo $errorTitle;?></div>
|
||||||
<div class="bookingThankYouMessageTitle"><?php echo $shownError;?></div>
|
<div class="bookingThankYouMessageTitle"><?php echo $shownError;?></div>
|
||||||
<div style="text-align:center; margin-top:30px;">
|
<div style="text-align:center; margin-top:30px;">
|
||||||
<a href="<?php echo $backUrl;?>" style="display:inline-block; padding:12px 28px; background:#000; color:#fff; text-decoration:none; border-radius:5px; font-size:16px;">Tilbake til bestilling</a>
|
<a href="<?php echo $backUrl;?>" style="display:inline-block; padding:12px 28px; background:#000; color:#fff; text-decoration:none; border-radius:5px; font-size:16px;"><?php echo $backLabel;?></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -177,7 +177,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="formRow">
|
<div class="formRow">
|
||||||
<label class="bookingEmailTitle">E-mail:</label>
|
<label class="bookingEmailTitle">E-mail:</label>
|
||||||
<input type="text" class="bookingEmail" name="guest_email" value="" required>
|
<input type="email" class="bookingEmail" name="guest_email" value="" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="formRow">
|
<div class="formRow">
|
||||||
<label class="bookingPhoneTitle">Phone:</label>
|
<label class="bookingPhoneTitle">Phone:</label>
|
||||||
@@ -364,12 +364,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAvailableWorkersByServiceCategorySlug(categorySlug){
|
function getAvailableWorkersByServiceCategorySlug(categorySlug){
|
||||||
|
// Only workers who can perform EVERY ticked service may be offered.
|
||||||
|
// The checkbox id IS the service_id (see the service list markup).
|
||||||
|
var selectedServiceIds = $('.service:checked').map(function(){
|
||||||
|
return this.id;
|
||||||
|
}).get();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '<?php echo base_url();?>ajax',
|
url: '<?php echo base_url();?>ajax',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
action:'getAvailableWorkersByServiceCategory',
|
action:'getAvailableWorkersByServiceCategory',
|
||||||
serv_cat_slug:categorySlug
|
serv_cat_slug:categorySlug,
|
||||||
|
service_ids:selectedServiceIds
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function() {
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -178,7 +178,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="formRow">
|
<div class="formRow">
|
||||||
<label class="bookingEmailTitle">E-mail:</label>
|
<label class="bookingEmailTitle">E-mail:</label>
|
||||||
<input type="text" class="bookingEmail" name="guest_email" value="" required>
|
<input type="email" class="bookingEmail" name="guest_email" value="" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="formRow">
|
<div class="formRow">
|
||||||
<label class="bookingPhoneTitle">Telefon:</label>
|
<label class="bookingPhoneTitle">Telefon:</label>
|
||||||
@@ -365,12 +365,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAvailableWorkersByServiceCategorySlug(categorySlug){
|
function getAvailableWorkersByServiceCategorySlug(categorySlug){
|
||||||
|
// Only workers who can perform EVERY ticked service may be offered.
|
||||||
|
// The checkbox id IS the service_id (see the service list markup).
|
||||||
|
var selectedServiceIds = $('.service:checked').map(function(){
|
||||||
|
return this.id;
|
||||||
|
}).get();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '<?php echo base_url();?>ajax',
|
url: '<?php echo base_url();?>ajax',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
action:'getAvailableWorkersByServiceCategory',
|
action:'getAvailableWorkersByServiceCategory',
|
||||||
serv_cat_slug:categorySlug
|
serv_cat_slug:categorySlug,
|
||||||
|
service_ids:selectedServiceIds
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function() {
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ if (is_array($services) && count($services) > 0) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="formRow">
|
<div class="formRow">
|
||||||
<label class="bookingEmailTitle">E-mail:</label>
|
<label class="bookingEmailTitle">E-mail:</label>
|
||||||
<input type="text" class="bookingEmail" name="guest_email" value="" required>
|
<input type="email" class="bookingEmail" name="guest_email" value="" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="formRow">
|
<div class="formRow">
|
||||||
<label class="bookingPhoneTitle">Telefon:</label>
|
<label class="bookingPhoneTitle">Telefon:</label>
|
||||||
@@ -363,12 +363,19 @@ if (is_array($services) && count($services) > 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAvailableWorkersByServiceCategorySlug(categorySlug){
|
function getAvailableWorkersByServiceCategorySlug(categorySlug){
|
||||||
|
// Only workers who can perform EVERY ticked service may be offered.
|
||||||
|
// The checkbox id IS the service_id (see the service list markup).
|
||||||
|
var selectedServiceIds = $('.service:checked').map(function(){
|
||||||
|
return this.id;
|
||||||
|
}).get();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '<?php echo base_url();?>ajax',
|
url: '<?php echo base_url();?>ajax',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
action:'getAvailableWorkersByServiceCategory',
|
action:'getAvailableWorkersByServiceCategory',
|
||||||
serv_cat_slug:categorySlug
|
serv_cat_slug:categorySlug,
|
||||||
|
service_ids:selectedServiceIds
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function() {
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo base_url();?>assets/img/massage/ollo_rose.png">
|
<img src="<?php echo base_url();?>assets/img/massage/section-icon.png">
|
||||||
<h2>About us</h2>
|
<h2>About us</h2>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -64,8 +64,8 @@
|
|||||||
<p>
|
<p>
|
||||||
With nearly 10 years of experience in the beauty industry, including many years
|
With nearly 10 years of experience in the beauty industry, including many years
|
||||||
of international experience, I thought it was time to open my own studio here in
|
of international experience, I thought it was time to open my own studio here in
|
||||||
Trondheim at Innheredsveien 92, where guests can park free of charge during
|
Trondheim at Innheredsveien 92. We have two private parking spaces that our
|
||||||
the treatments.
|
guests can use free of charge during their treatment, subject to availability.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
It is an extra pleasure for me that I managed to find a traditional place where
|
It is an extra pleasure for me that I managed to find a traditional place where
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo base_url();?>assets/img/massage/ollo_rose.png">
|
<img src="<?php echo base_url();?>assets/img/massage/section-icon.png">
|
||||||
<h2 class="nyitva">Treatments</h2>
|
<h2 class="nyitva">Treatments</h2>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
@@ -189,7 +189,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo SITEURL;?>assets/img/massage/ollo_rose.png">
|
<img src="<?php echo SITEURL;?>assets/img/massage/section-icon.png">
|
||||||
<h2>Prices</h2>
|
<h2>Prices</h2>
|
||||||
<h4 class="prices-note">The stated treatment time is hands-on treatment time. The appointment reserved for you is slightly longer, allowing time to change and to pay.</h4>
|
<h4 class="prices-note">The stated treatment time is hands-on treatment time. The appointment reserved for you is slightly longer, allowing time to change and to pay.</h4>
|
||||||
</header>
|
</header>
|
||||||
@@ -258,9 +258,9 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo SITEURL;?>assets/img/massage/ollo_rose.png">
|
<img src="<?php echo SITEURL;?>assets/img/massage/section-icon.png">
|
||||||
<h2 class="nyitva">Opening Hours</h2>
|
<h2 class="nyitva">Opening Hours</h2>
|
||||||
<h4>Free customer parking during your treatment</h4>
|
<h4>2 free customer parking spaces – subject to availability</h4>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
<div class="nyitvatartas">
|
<div class="nyitvatartas">
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo base_url();?>assets/img/massage/ollo_rose.png">
|
<img src="<?php echo base_url();?>assets/img/massage/section-icon.png">
|
||||||
<h2>Rólunk</h2>
|
<h2>Rólunk</h2>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -62,8 +62,9 @@
|
|||||||
<p>
|
<p>
|
||||||
Közel 10 éves szépségipari, és közte több éves nemzetközi tapasztalattal
|
Közel 10 éves szépségipari, és közte több éves nemzetközi tapasztalattal
|
||||||
elérkezettnek láttam az időt, hogy megnyissam saját stúdiómat itt
|
elérkezettnek láttam az időt, hogy megnyissam saját stúdiómat itt
|
||||||
Trondheimben, az Innheredsveien 92 szám alatt, ahol a vendégeknek lehetősége
|
Trondheimben, az Innheredsveien 92 szám alatt. Két privát parkolóhellyel
|
||||||
van ingyenes parkolásra is a kezelések időtartama alatt.
|
rendelkezünk, amelyeket vendégeink a kezelés ideje alatt ingyenesen
|
||||||
|
használhatnak, amennyiben van szabad hely.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Extra öröm számomra, hogy egy olyan tradicionális helyet sikerült találnom,
|
Extra öröm számomra, hogy egy olyan tradicionális helyet sikerült találnom,
|
||||||
@@ -110,7 +111,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo base_url();?>assets/img/massage/ollo_rose.png">
|
<img src="<?php echo base_url();?>assets/img/massage/section-icon.png">
|
||||||
<h2 class="nyitva">Szolgáltatások</h2>
|
<h2 class="nyitva">Szolgáltatások</h2>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
@@ -187,7 +188,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo SITEURL;?>assets/img/massage/ollo_rose.png">
|
<img src="<?php echo SITEURL;?>assets/img/massage/section-icon.png">
|
||||||
<h2>Árlista</h2>
|
<h2>Árlista</h2>
|
||||||
<h4 class="prices-note">A feltüntetett kezelési idő a tényleges masszázsidőt jelenti. A lefoglalt időpont ennél valamivel hosszabb, hogy legyen idő az át­öltözésre és a fizetésre.</h4>
|
<h4 class="prices-note">A feltüntetett kezelési idő a tényleges masszázsidőt jelenti. A lefoglalt időpont ennél valamivel hosszabb, hogy legyen idő az át­öltözésre és a fizetésre.</h4>
|
||||||
</header>
|
</header>
|
||||||
@@ -256,9 +257,9 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo SITEURL;?>assets/img/massage/ollo_rose.png">
|
<img src="<?php echo SITEURL;?>assets/img/massage/section-icon.png">
|
||||||
<h2 class="nyitva">Nyitvatartás</h2>
|
<h2 class="nyitva">Nyitvatartás</h2>
|
||||||
<h4>Ingyenes vendégparkolás a kezelés ideje alatt</h4>
|
<h4>2 ingyenes vendégparkoló – szabad hely esetén</h4>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
<div class="nyitvatartas">
|
<div class="nyitvatartas">
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo base_url();?>assets/img/massage/ollo_rose.png">
|
<img src="<?php echo base_url();?>assets/img/massage/section-icon.png">
|
||||||
<h2>Om oss</h2>
|
<h2>Om oss</h2>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -64,8 +64,8 @@
|
|||||||
<p>
|
<p>
|
||||||
Med nesten 10 års erfaring fra skjønnhetsindustrien, inkludert mange års
|
Med nesten 10 års erfaring fra skjønnhetsindustrien, inkludert mange års
|
||||||
internasjonal erfaring, tenkte jeg det var på tide å åpne mitt eget studio her i
|
internasjonal erfaring, tenkte jeg det var på tide å åpne mitt eget studio her i
|
||||||
Trondheim i Innheredsveien 92, hvor gjestene har mulighet til å parkere gratis
|
Trondheim i Innheredsveien 92. Vi har to private parkeringsplasser som våre
|
||||||
under behandlingene.
|
gjester kan benytte gratis under behandlingen, dersom det er ledig kapasitet.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Det er en ekstra glede for meg at jeg klarte å finne et tradisjonelt sted hvor det
|
Det er en ekstra glede for meg at jeg klarte å finne et tradisjonelt sted hvor det
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo base_url();?>assets/img/massage/ollo_rose.png">
|
<img src="<?php echo base_url();?>assets/img/massage/section-icon.png">
|
||||||
<h2 class="nyitva">Behandlinger</h2>
|
<h2 class="nyitva">Behandlinger</h2>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
@@ -189,7 +189,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo SITEURL;?>assets/img/massage/ollo_rose.png">
|
<img src="<?php echo SITEURL;?>assets/img/massage/section-icon.png">
|
||||||
<h2>Priser</h2>
|
<h2>Priser</h2>
|
||||||
<h4 class="prices-note">Oppgitt behandlingstid er ren behandlingstid. Timen som reserveres er noe lengre, slik at det er tid til av- og påkledning og betaling.</h4>
|
<h4 class="prices-note">Oppgitt behandlingstid er ren behandlingstid. Timen som reserveres er noe lengre, slik at det er tid til av- og påkledning og betaling.</h4>
|
||||||
</header>
|
</header>
|
||||||
@@ -258,9 +258,9 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
<img src="<?php echo SITEURL;?>assets/img/massage/ollo_rose.png">
|
<img src="<?php echo SITEURL;?>assets/img/massage/section-icon.png">
|
||||||
<h2 class="nyitva">Åpningstider</h2>
|
<h2 class="nyitva">Åpningstider</h2>
|
||||||
<h4>Gratis kunderparking under behandlingen</h4>
|
<h4>2 gratis kundeparkeringsplasser – ved ledig kapasitet</h4>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
<div class="nyitvatartas">
|
<div class="nyitvatartas">
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
'back' => 'Tilbake',
|
'back' => 'Tilbake',
|
||||||
'send' => 'Bekreft endring',
|
'send' => 'Bekreft endring',
|
||||||
'no_times' => 'Ingen ledige tider denne dagen!',
|
'no_times' => 'Ingen ledige tider denne dagen!',
|
||||||
|
'no_workers' => 'Ingen medarbeidere utfører alle de valgte tjenestene.',
|
||||||
'expired_msg' => 'Denne bestillingen er allerede passert og kan ikke endres.',
|
'expired_msg' => 'Denne bestillingen er allerede passert og kan ikke endres.',
|
||||||
'cutoff_msg' => 'Bestillingen er innen 24 timer og kan ikke endres eller avbestilles.',
|
'cutoff_msg' => 'Bestillingen er innen 24 timer og kan ikke endres eller avbestilles.',
|
||||||
),
|
),
|
||||||
@@ -53,6 +54,7 @@
|
|||||||
'back' => 'Vissza',
|
'back' => 'Vissza',
|
||||||
'send' => 'Módosítás megerősítése',
|
'send' => 'Módosítás megerősítése',
|
||||||
'no_times' => 'Ezen a napon nincs szabad időpont!',
|
'no_times' => 'Ezen a napon nincs szabad időpont!',
|
||||||
|
'no_workers' => 'Egyik kolléga sem végzi el az összes kiválasztott szolgáltatást.',
|
||||||
'expired_msg' => 'Ez a foglalás már elmúlt, nem módosítható.',
|
'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ő.',
|
'cutoff_msg' => 'A foglalás 24 órán belül van, nem módosítható vagy törölhető.',
|
||||||
),
|
),
|
||||||
@@ -76,6 +78,7 @@
|
|||||||
'back' => 'Back',
|
'back' => 'Back',
|
||||||
'send' => 'Confirm changes',
|
'send' => 'Confirm changes',
|
||||||
'no_times' => 'There is no available time on this day!',
|
'no_times' => 'There is no available time on this day!',
|
||||||
|
'no_workers' => 'No staff member performs all of the selected services.',
|
||||||
'expired_msg' => 'This booking has already passed and cannot be modified.',
|
'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.',
|
'cutoff_msg' => 'Your booking is within 24 hours and can no longer be changed or cancelled.',
|
||||||
),
|
),
|
||||||
@@ -246,6 +249,7 @@
|
|||||||
onsubmit="return confirm('<?php echo addslashes($l['cancel_confirm']); ?>');">
|
onsubmit="return confirm('<?php echo addslashes($l['cancel_confirm']); ?>');">
|
||||||
<input type="hidden" name="token" value="<?php echo htmlspecialchars($token); ?>">
|
<input type="hidden" name="token" value="<?php echo htmlspecialchars($token); ?>">
|
||||||
<input type="hidden" name="lang" value="<?php echo htmlspecialchars($lang); ?>">
|
<input type="hidden" name="lang" value="<?php echo htmlspecialchars($lang); ?>">
|
||||||
|
<input type="hidden" name="subpage" value="<?php echo htmlspecialchars($subpage); ?>">
|
||||||
<button type="submit" class="manage-cancel-btn"><?php echo $l['cancel_btn']; ?></button>
|
<button type="submit" class="manage-cancel-btn"><?php echo $l['cancel_btn']; ?></button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -445,6 +449,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
var noTimesMsg = '<?php echo addslashes($l['no_times']); ?>';
|
var noTimesMsg = '<?php echo addslashes($l['no_times']); ?>';
|
||||||
|
var noWorkersMsg = '<?php echo addslashes($l['no_workers']); ?>';
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
recalcServices();
|
recalcServices();
|
||||||
@@ -543,16 +548,30 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAvailableWorkersByServiceCategorySlug(categorySlug){
|
function getAvailableWorkersByServiceCategorySlug(categorySlug){
|
||||||
|
// Same rule as the new-booking form: only workers who can perform EVERY
|
||||||
|
// ticked service may be offered. The checkbox id IS the service_id.
|
||||||
|
var selectedServiceIds = $('.service:checked').map(function(){
|
||||||
|
return this.id;
|
||||||
|
}).get();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '<?php echo base_url(); ?>ajax',
|
url: '<?php echo base_url(); ?>ajax',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: { action: 'getAvailableWorkersByServiceCategory', serv_cat_slug: categorySlug },
|
data: {
|
||||||
|
action: 'getAvailableWorkersByServiceCategory',
|
||||||
|
serv_cat_slug: categorySlug,
|
||||||
|
service_ids: selectedServiceIds
|
||||||
|
},
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
}).done(function(result){
|
}).done(function(result){
|
||||||
$('#workerTable').html(result.workerListShow);
|
|
||||||
if(result.workers && result.workers.length > 0){
|
if(result.workers && result.workers.length > 0){
|
||||||
|
$('#workerTable').html(result.workerListShow);
|
||||||
setWorker(result.workers[0].worker_id);
|
setWorker(result.workers[0].worker_id);
|
||||||
}
|
}
|
||||||
|
else{
|
||||||
|
$('#workerTable').html('<div class="noWorkerMessage">' + noWorkersMsg + '</div>');
|
||||||
|
setWorker('');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,6 +664,13 @@
|
|||||||
function setWorker(worker_id){
|
function setWorker(worker_id){
|
||||||
$('#worker_id').val(worker_id);
|
$('#worker_id').val(worker_id);
|
||||||
$('.serviceBookingWorkerContainer').hide().html('');
|
$('.serviceBookingWorkerContainer').hide().html('');
|
||||||
|
|
||||||
|
// The worker (or the service set that produced this list) has changed, so
|
||||||
|
// a time picked for the previous one is stale. Clearing it also disables
|
||||||
|
// the submit button via the #booking_time change handler.
|
||||||
|
$('#booking_time').val('').trigger('change');
|
||||||
|
$('#serviceBookingTime').html('').hide();
|
||||||
|
$('.availableBookingTime').removeClass('selectedTime');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3364,6 +3364,11 @@ input, select, textarea {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Worker bio with no photo strip alongside - let the text use the full width. */
|
||||||
|
.bemutatkozas-dolgozo.no-gallery .bem-dolgozo-bal{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Treatments - Our Services */
|
/* Treatments - Our Services */
|
||||||
@@ -4576,3 +4581,27 @@ input, select, textarea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ *
|
||||||
|
* Treatments grid: keep the six icons level across the two columns.
|
||||||
|
*
|
||||||
|
* .service-text reserves padding-bottom for the 80px floated icon but has no
|
||||||
|
* height, so every block sizes to its own text. Measured on the live page the
|
||||||
|
* left column runs 155/155px while the right runs 185/206px - its titles wrap
|
||||||
|
* to two lines - so the icons drift further apart with each row (0, 30, 81px).
|
||||||
|
*
|
||||||
|
* Fix: trim the reserved padding and give every block the same floor, so all
|
||||||
|
* six are identical and the rows line up. 195px clears the tallest block and
|
||||||
|
* leaves room for a three-line title at the narrow end of the desktop range.
|
||||||
|
*
|
||||||
|
* Desktop only: below 981px .one-third becomes full width and the columns
|
||||||
|
* stack, where a floor would only add dead space.
|
||||||
|
* ------------------------------------------------------------------ */
|
||||||
|
@media screen and (min-width: 981px) {
|
||||||
|
|
||||||
|
.service-area .one-third .service .service-text {
|
||||||
|
padding-bottom: 55px;
|
||||||
|
min-height: 195px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -3191,7 +3191,7 @@ input, select, textarea {
|
|||||||
|
|
||||||
.common-home .box-heading:before, .box.related .box-heading:before {
|
.common-home .box-heading:before, .box.related .box-heading:before {
|
||||||
content: "";
|
content: "";
|
||||||
background: url(../../assets/img/massage/ollo_rose.png) no-repeat scroll;
|
background: url(../../assets/img/massage/section-icon.png) no-repeat scroll;
|
||||||
width: 75px;
|
width: 75px;
|
||||||
height: 65px;
|
height: 65px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -3297,6 +3297,12 @@ input, select, textarea {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
/* was `margin: auto` - on a flex item that centres it on BOTH axes. */
|
/* was `margin: auto` - on a flex item that centres it on BOTH axes. */
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
/* Optical alignment with the bio text. Both columns are flex-start, so
|
||||||
|
their boxes DO start level - but body text is 13pt/1.75em, which puts
|
||||||
|
6.5px of half-leading above the first line's glyphs, while an inline
|
||||||
|
image starts flush at the box top. Without this the strip reads as
|
||||||
|
sitting high. (1.75em - 1em) / 2 = 0.375em = that same 6.5px. */
|
||||||
|
padding-top: 0.375em;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4488,7 +4494,9 @@ input, select, textarea {
|
|||||||
|
|
||||||
.bemutatkozas-dolgozo{display: block;}
|
.bemutatkozas-dolgozo{display: block;}
|
||||||
.bem-dolgozo-bal {width: 100%;}
|
.bem-dolgozo-bal {width: 100%;}
|
||||||
.bem-dolgozo-jobb {width: 100%; float: none; text-align: center;}
|
/* Container is display:block here, so the strip no longer sits
|
||||||
|
beside the text and the half-leading correction is unwanted. */
|
||||||
|
.bem-dolgozo-jobb {width: 100%; float: none; text-align: center; padding-top: 0;}
|
||||||
|
|
||||||
#czcategorylistblock .czcategorylist-wrapper .category_lists .one-half .category-content {width: 100%; }
|
#czcategorylistblock .czcategorylist-wrapper .category_lists .one-half .category-content {width: 100%; }
|
||||||
.one-half{width:100%; float: none;}
|
.one-half{width:100%; float: none;}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |