update
This commit is contained in:
BIN
wp-content/.DS_Store
vendored
BIN
wp-content/.DS_Store
vendored
Binary file not shown.
BIN
wp-content/languages/.DS_Store
vendored
BIN
wp-content/languages/.DS_Store
vendored
Binary file not shown.
BIN
wp-content/plugins/.DS_Store
vendored
BIN
wp-content/plugins/.DS_Store
vendored
Binary file not shown.
@@ -1403,3 +1403,164 @@ button.carei-reservation-trigger:hover {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Carei Map Widget
|
||||
═══════════════════════════════════════════ */
|
||||
|
||||
.carei-map {
|
||||
position: relative;
|
||||
max-width: 520px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.carei-map__svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.carei-map__pin {
|
||||
cursor: pointer;
|
||||
transition: r 0.2s ease, fill 0.2s ease;
|
||||
}
|
||||
|
||||
.carei-map__pin:hover,
|
||||
.carei-map__pin--active {
|
||||
r: 8;
|
||||
}
|
||||
|
||||
.carei-map__tooltip {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
transform: translate(-50%, -100%);
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.carei-map__tooltip-content {
|
||||
background: var(--carei-blue);
|
||||
color: var(--carei-white);
|
||||
font-family: var(--carei-font);
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
padding: 10px 14px;
|
||||
border-radius: var(--carei-radius);
|
||||
white-space: pre-line;
|
||||
min-width: 160px;
|
||||
max-width: 260px;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.carei-map__tooltip-content::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border: 6px solid transparent;
|
||||
border-top-color: var(--carei-blue);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Carei Cities Widget
|
||||
═══════════════════════════════════════════ */
|
||||
|
||||
.carei-cities {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 6px 0;
|
||||
font-family: var(--carei-font);
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: var(--carei-blue);
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.carei-cities__item {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.carei-cities__item::after {
|
||||
content: '|';
|
||||
padding: 0 14px;
|
||||
opacity: 0.35;
|
||||
font-weight: 300;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.carei-cities__item:last-child::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.carei-cities {
|
||||
font-size: 13px;
|
||||
gap: 4px 0;
|
||||
}
|
||||
.carei-cities__item::after {
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Carei Branches Widget
|
||||
═══════════════════════════════════════════ */
|
||||
|
||||
.carei-branches {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
font-family: var(--carei-font);
|
||||
color: var(--carei-white);
|
||||
}
|
||||
|
||||
.carei-branches__item {
|
||||
padding: 24px 16px;
|
||||
text-align: center;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.carei-branches__name {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.carei-branches__street,
|
||||
.carei-branches__zip-city {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.carei-branches {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.carei-branches {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
.carei-branches__item {
|
||||
padding: 18px 12px;
|
||||
}
|
||||
.carei-branches__name {
|
||||
font-size: 14px;
|
||||
}
|
||||
.carei-branches__street,
|
||||
.carei-branches__zip-city {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.carei-branches {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1265,6 +1265,110 @@
|
||||
initClearErrors();
|
||||
initAbroad();
|
||||
initSubmit();
|
||||
initMap();
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Carei Map — dynamic pins & tooltips
|
||||
═══════════════════════════════════════════ */
|
||||
|
||||
function initMap() {
|
||||
var mapEl = document.querySelector('.carei-map');
|
||||
if (!mapEl) return;
|
||||
|
||||
var pins;
|
||||
try {
|
||||
pins = JSON.parse(mapEl.getAttribute('data-pins') || '[]');
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
if (!pins.length) return;
|
||||
|
||||
var svg = mapEl.querySelector('.carei-map__svg');
|
||||
var pinsGroup = svg.querySelector('.carei-map__pins');
|
||||
var tooltip = mapEl.querySelector('.carei-map__tooltip');
|
||||
var tooltipContent = mapEl.querySelector('.carei-map__tooltip-content');
|
||||
if (!svg || !pinsGroup || !tooltip || !tooltipContent) return;
|
||||
|
||||
var activePin = null;
|
||||
var SVG_NS = 'http://www.w3.org/2000/svg';
|
||||
|
||||
pins.forEach(function (pin) {
|
||||
var circle = document.createElementNS(SVG_NS, 'circle');
|
||||
circle.setAttribute('cx', pin.x);
|
||||
circle.setAttribute('cy', pin.y);
|
||||
circle.setAttribute('r', '6');
|
||||
circle.setAttribute('fill', '#FF0000');
|
||||
circle.setAttribute('class', 'carei-map__pin');
|
||||
circle.setAttribute('data-city', pin.city);
|
||||
circle.setAttribute('data-address', pin.address || '');
|
||||
|
||||
circle.addEventListener('mouseenter', function () {
|
||||
showTooltip(pin, circle);
|
||||
});
|
||||
circle.addEventListener('mouseleave', function () {
|
||||
if (activePin !== circle) {
|
||||
hideTooltip();
|
||||
}
|
||||
});
|
||||
circle.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
if (activePin === circle) {
|
||||
activePin = null;
|
||||
circle.classList.remove('carei-map__pin--active');
|
||||
hideTooltip();
|
||||
} else {
|
||||
if (activePin) activePin.classList.remove('carei-map__pin--active');
|
||||
activePin = circle;
|
||||
circle.classList.add('carei-map__pin--active');
|
||||
showTooltip(pin, circle);
|
||||
}
|
||||
});
|
||||
|
||||
pinsGroup.appendChild(circle);
|
||||
});
|
||||
|
||||
// Click outside closes tooltip
|
||||
document.addEventListener('click', function () {
|
||||
if (activePin) {
|
||||
activePin.classList.remove('carei-map__pin--active');
|
||||
activePin = null;
|
||||
hideTooltip();
|
||||
}
|
||||
});
|
||||
|
||||
function showTooltip(pin, circle) {
|
||||
// Build tooltip text: address lines + bold city
|
||||
var addr = pin.address || '';
|
||||
var lines = addr ? addr.replace(/\\n/g, '\n') : pin.city;
|
||||
tooltipContent.textContent = '';
|
||||
|
||||
// If address has postal code, format nicely
|
||||
if (addr) {
|
||||
tooltipContent.innerHTML = addr.replace(/\n/g, '<br>');
|
||||
} else {
|
||||
tooltipContent.textContent = pin.city;
|
||||
}
|
||||
|
||||
// Position tooltip relative to map container
|
||||
var svgRect = svg.getBoundingClientRect();
|
||||
var mapRect = mapEl.getBoundingClientRect();
|
||||
var svgWidth = svg.viewBox.baseVal.width || 600;
|
||||
var svgHeight = svg.viewBox.baseVal.height || 570;
|
||||
var scaleX = svgRect.width / svgWidth;
|
||||
var scaleY = svgRect.height / svgHeight;
|
||||
|
||||
var left = (svgRect.left - mapRect.left) + pin.x * scaleX;
|
||||
var top = (svgRect.top - mapRect.top) + pin.y * scaleY;
|
||||
|
||||
tooltip.style.display = 'block';
|
||||
tooltip.style.left = left + 'px';
|
||||
tooltip.style.top = top + 'px';
|
||||
}
|
||||
|
||||
function hideTooltip() {
|
||||
tooltip.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
|
||||
@@ -88,6 +88,15 @@ add_action( 'elementor/widgets/register', function ( $widgets_manager ) {
|
||||
|
||||
require_once CAREI_RESERVATION_PATH . 'includes/class-search-widget.php';
|
||||
$widgets_manager->register( new Carei_Search_Widget() );
|
||||
|
||||
require_once CAREI_RESERVATION_PATH . 'includes/class-map-widget.php';
|
||||
$widgets_manager->register( new Carei_Map_Widget() );
|
||||
|
||||
require_once CAREI_RESERVATION_PATH . 'includes/class-cities-widget.php';
|
||||
$widgets_manager->register( new Carei_Cities_Widget() );
|
||||
|
||||
require_once CAREI_RESERVATION_PATH . 'includes/class-branches-widget.php';
|
||||
$widgets_manager->register( new Carei_Branches_Widget() );
|
||||
} );
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor Widget: Carei Branches — grid oddziałów z adresami.
|
||||
*/
|
||||
class Carei_Branches_Widget extends \Elementor\Widget_Base {
|
||||
|
||||
const NAME_FIXES = array(
|
||||
'BYDGOSZC' => 'BYDGOSZCZ',
|
||||
'GORZÓW WIE' => 'GORZÓW WIELKOPOLSKI',
|
||||
'RZSZÓW' => 'RZESZÓW',
|
||||
'SK-KAM' => '',
|
||||
);
|
||||
|
||||
public function get_name() {
|
||||
return 'carei-branches';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return 'Carei Branches';
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-posts-grid';
|
||||
}
|
||||
|
||||
public function get_categories() {
|
||||
return array( 'general' );
|
||||
}
|
||||
|
||||
public function get_style_depends() {
|
||||
return array( 'carei-reservation-css' );
|
||||
}
|
||||
|
||||
protected function register_controls() {}
|
||||
|
||||
/**
|
||||
* Clean branch name and return base city.
|
||||
*/
|
||||
private static function clean_name( $raw ) {
|
||||
$name = trim( $raw );
|
||||
$name = preg_replace( '/\s+[DL]$/u', '', $name );
|
||||
if ( isset( self::NAME_FIXES[ $name ] ) ) {
|
||||
$name = self::NAME_FIXES[ $name ];
|
||||
}
|
||||
if ( empty( $name ) || preg_match( '/^[A-Z]{2,4}-[A-Z]{2,4}$/u', $name ) ) {
|
||||
return '';
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unique branches with addresses.
|
||||
*/
|
||||
private function get_branches_data() {
|
||||
$api = Carei_Softra_API::get_instance();
|
||||
if ( null === $api ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$branches = $api->get_branches_cached();
|
||||
if ( is_wp_error( $branches ) || ! is_array( $branches ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$result = array();
|
||||
$seen = array();
|
||||
|
||||
foreach ( $branches as $b ) {
|
||||
$raw_name = isset( $b['name'] ) ? $b['name'] : '';
|
||||
$city = self::clean_name( $raw_name );
|
||||
if ( empty( $city ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$norm = mb_strtolower( $city, 'UTF-8' );
|
||||
if ( isset( $seen[ $norm ] ) ) {
|
||||
continue;
|
||||
}
|
||||
$seen[ $norm ] = true;
|
||||
|
||||
$display_city = mb_convert_case( $city, MB_CASE_TITLE, 'UTF-8' );
|
||||
$street = isset( $b['street'] ) ? trim( $b['street'] ) : '';
|
||||
$zip = isset( $b['zipCode'] ) ? trim( (string) $b['zipCode'] ) : '';
|
||||
$api_city = isset( $b['city'] ) ? trim( $b['city'] ) : '';
|
||||
$api_city_tc = mb_convert_case( $api_city, MB_CASE_TITLE, 'UTF-8' );
|
||||
|
||||
// Format street
|
||||
if ( $street ) {
|
||||
$street_lower = mb_strtolower( $street, 'UTF-8' );
|
||||
$has_prefix = preg_match( '/^(ul\.|al\.|pl\.|os\.)/u', $street_lower );
|
||||
$street = $has_prefix ? $street : 'ul. ' . $street;
|
||||
}
|
||||
|
||||
$result[] = array(
|
||||
'name' => 'Oddział ' . $display_city,
|
||||
'street' => $street,
|
||||
'zipCity' => trim( $zip . ' ' . $api_city_tc ),
|
||||
);
|
||||
}
|
||||
|
||||
usort( $result, function ( $a, $b ) {
|
||||
return strcmp( $a['name'], $b['name'] );
|
||||
} );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function render() {
|
||||
$branches = $this->get_branches_data();
|
||||
if ( empty( $branches ) ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div class="carei-branches">
|
||||
<?php foreach ( $branches as $branch ) : ?>
|
||||
<div class="carei-branches__item">
|
||||
<div class="carei-branches__name"><?php echo esc_html( $branch['name'] ); ?></div>
|
||||
<?php if ( $branch['street'] ) : ?>
|
||||
<div class="carei-branches__street"><?php echo esc_html( $branch['street'] ); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ( $branch['zipCity'] ) : ?>
|
||||
<div class="carei-branches__zip-city"><?php echo esc_html( $branch['zipCity'] ); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor Widget: Carei Cities — siatka miast oddziałów.
|
||||
*/
|
||||
class Carei_Cities_Widget extends \Elementor\Widget_Base {
|
||||
|
||||
public function get_name() {
|
||||
return 'carei-cities';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return 'Carei Cities';
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-bullet-list';
|
||||
}
|
||||
|
||||
public function get_categories() {
|
||||
return array( 'general' );
|
||||
}
|
||||
|
||||
public function get_style_depends() {
|
||||
return array( 'carei-reservation-css' );
|
||||
}
|
||||
|
||||
public function get_script_depends() {
|
||||
return array( 'carei-reservation-js' );
|
||||
}
|
||||
|
||||
protected function register_controls() {}
|
||||
|
||||
/**
|
||||
* Truncated / misspelled names from Softra API → correct full city names.
|
||||
*/
|
||||
const NAME_FIXES = array(
|
||||
'BYDGOSZC' => 'BYDGOSZCZ',
|
||||
'GORZÓW WIE' => 'GORZÓW WIELKOPOLSKI',
|
||||
'RZSZÓW' => 'RZESZÓW',
|
||||
'SK-KAM' => '',
|
||||
);
|
||||
|
||||
/**
|
||||
* Clean branch name: handle D(Dworzec)/L suffixes, fix truncated names, title-case.
|
||||
* API returns names like "GDAŃSK D", "GDAŃSK L", "BYDGOSZC D", "SK-KAM", "GORZÓW WIE".
|
||||
* D-suffix branches without a base variant → "Oddział [City]".
|
||||
*/
|
||||
private static function clean_city_name( $raw ) {
|
||||
$name = trim( $raw );
|
||||
// Detect D (Dworzec) suffix before stripping
|
||||
$is_dworzec = (bool) preg_match( '/\s+D$/u', $name );
|
||||
// Strip trailing single-letter suffixes (D=Dworzec, L=Lotnisko/inne)
|
||||
$name = preg_replace( '/\s+[DL]$/u', '', $name );
|
||||
// Fix known truncated/misspelled names
|
||||
if ( isset( self::NAME_FIXES[ $name ] ) ) {
|
||||
$name = self::NAME_FIXES[ $name ];
|
||||
}
|
||||
// Skip empty or non-city codes
|
||||
if ( empty( $name ) || preg_match( '/^[A-Z]{2,4}-[A-Z]{2,4}$/u', $name ) ) {
|
||||
return '';
|
||||
}
|
||||
// Title-case: "GDAŃSK" → "Gdańsk"
|
||||
$name = mb_convert_case( $name, MB_CASE_TITLE, 'UTF-8' );
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract unique city names from branches.
|
||||
*/
|
||||
private function get_city_names() {
|
||||
$api = Carei_Softra_API::get_instance();
|
||||
if ( null === $api ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$branches = $api->get_branches_cached();
|
||||
if ( is_wp_error( $branches ) || ! is_array( $branches ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$cities = array();
|
||||
$seen = array();
|
||||
|
||||
foreach ( $branches as $b ) {
|
||||
$name = isset( $b['name'] ) ? $b['name'] : '';
|
||||
$city = self::clean_city_name( $name );
|
||||
if ( empty( $city ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$norm = mb_strtolower( $city, 'UTF-8' );
|
||||
if ( isset( $seen[ $norm ] ) ) {
|
||||
continue;
|
||||
}
|
||||
$seen[ $norm ] = true;
|
||||
$cities[] = $city;
|
||||
}
|
||||
|
||||
sort( $cities );
|
||||
return $cities;
|
||||
}
|
||||
|
||||
protected function render() {
|
||||
$cities = $this->get_city_names();
|
||||
if ( empty( $cities ) ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div class="carei-cities">
|
||||
<?php foreach ( $cities as $i => $city ) : ?>
|
||||
<span class="carei-cities__item"><?php echo esc_html( $city ); ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -117,6 +117,13 @@ class Carei_REST_Proxy {
|
||||
),
|
||||
) );
|
||||
|
||||
// GET /branches-full (cached, full branch objects with descriptions)
|
||||
register_rest_route( self::NAMESPACE, '/branches-full', array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_branches_full' ),
|
||||
'permission_callback' => '__return_true',
|
||||
) );
|
||||
|
||||
// GET /agreements
|
||||
register_rest_route( self::NAMESPACE, '/agreements', array(
|
||||
'methods' => 'GET',
|
||||
@@ -276,6 +283,14 @@ class Carei_REST_Proxy {
|
||||
) );
|
||||
}
|
||||
|
||||
public function get_branches_full( WP_REST_Request $request ) {
|
||||
$api = $this->api();
|
||||
if ( is_wp_error( $api ) ) {
|
||||
return $api;
|
||||
}
|
||||
return $this->respond( $api->get_branches_cached() );
|
||||
}
|
||||
|
||||
public function get_agreements( WP_REST_Request $request ) {
|
||||
$api = $this->api();
|
||||
if ( is_wp_error( $api ) ) {
|
||||
|
||||
@@ -153,6 +153,26 @@ class Carei_Softra_API {
|
||||
return $this->request( 'GET', '/branch/list' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get branches with 60-min transient cache.
|
||||
*/
|
||||
public function get_branches_cached() {
|
||||
$cache_key = 'carei_branches_list';
|
||||
$cached = get_transient( $cache_key );
|
||||
if ( false !== $cached ) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$branches = $this->get_branches();
|
||||
if ( is_wp_error( $branches ) || ! is_array( $branches ) ) {
|
||||
return is_wp_error( $branches ) ? $branches : array();
|
||||
}
|
||||
|
||||
set_transient( $cache_key, $branches, HOUR_IN_SECONDS );
|
||||
|
||||
return $branches;
|
||||
}
|
||||
|
||||
public function get_all_car_classes() {
|
||||
return $this->request( 'GET', '/car/class/listAll' );
|
||||
}
|
||||
@@ -170,7 +190,7 @@ class Carei_Softra_API {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$branches = $this->get_branches();
|
||||
$branches = $this->get_branches_cached();
|
||||
if ( is_wp_error( $branches ) || ! is_array( $branches ) ) {
|
||||
return is_wp_error( $branches ) ? $branches : array();
|
||||
}
|
||||
|
||||
BIN
wp-content/themes/.DS_Store
vendored
BIN
wp-content/themes/.DS_Store
vendored
Binary file not shown.
Reference in New Issue
Block a user