Making widget.js

This commit is contained in:
2025-12-18 15:56:59 +01:00
parent 58e1112bd2
commit 8b90b0a704
4 changed files with 437 additions and 3 deletions

View File

@@ -1,4 +1,5 @@
<?php <?php
header('Access-Control-Allow-Origin: *');
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
// ==== DB CONFIG ==== // ==== DB CONFIG ====

6
salony/README.md Normal file
View File

@@ -0,0 +1,6 @@
For using "widget.js"
past this code inside page:
<div id="salony-widget"></div>
<script src="https://serwer1852487.home.pl/Geo/salony/widget.js"></script>
<script async src="https://maps.googleapis.com/maps/api/js?key=KEY"></script>

View File

@@ -3,7 +3,6 @@ require __DIR__ . '/../db.php';
if ($_POST) { if ($_POST) {
// === Dodajemy miejsce === // === Dodajemy miejsce ===
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
INSERT INTO salon_places (name, woj) INSERT INTO salon_places (name, woj)
@@ -13,13 +12,12 @@ if ($_POST) {
$_POST['name'], $_POST['name'],
$_POST['woj'] ?: null $_POST['woj'] ?: null
]); ]);
$placeId = $pdo->lastInsertId(); $placeId = $pdo->lastInsertId();
// === Dodajemy sklep (shop) === // === Dodajemy sklep (shop) ===
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
INSERT INTO shops (place_id, address, open_hours, url_address, url_shop, lat, lng) INSERT INTO shops (place_id, address, open_hours, url_address, url_shop, lat, lng)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
"); ");
$stmt->execute([ $stmt->execute([
$placeId, $placeId,

429
salony/widget.js Normal file
View File

@@ -0,0 +1,429 @@
(function () {
const API_URL = 'https://serwer1852487.home.pl/Geo/api/salony/';
const root = document.getElementById('salony-widget');
if (!root) return;
root.innerHTML = `
<div class="mapFull">
<div id="map"></div>
</div>
<div class="mapResults">
<h1>ZNAJDŹ SALON</h1>
<div class="searchCont">
<input id="tinp" type="text" placeholder="ZNAJDŹ SKLEP">
<ul class="suggestList">
<li class="firstSuggest"></li>
</ul>
<label for="tinp">Wpisz Miasto bądź województwo</label>
<button id="search">szukaj
<i class="fas fa-search" style="min-height: 14px;"></i>
</button>
</div>
<p class="resultQ">
WYNIKI: <span class="dynamic">0</span>
</p>
<ul id="shopList">
<li class="forCloning place">
<input type="radio" name="shopPlace">
<div class="shopHiddenInfo">
<p class="lat"></p>
<p class="lng"></p>
</div>
<div class="content">
<h2 class="shopAddress"></h2>
<p>GODZINY OTWARCIA:</p>
<p class="shopHours"></p>
<a class="link" target="_blank">Wskazówki dojazdu</a>
</div>
</li>
</ul>
</div>
`;
injectStyles();
let map;
let markers = [];
let places = [];
var iconB = 'https://moodo.pl/data/include/cms/salony-1/mark2.png'
var iconW = 'https://moodo.pl/data/include/cms/salony-1/markw2.png'
fetch(API_URL)
.then(r => r.json())
.then(data => {
places = data;
initMap();
buildSuggestList();
renderList(data);
renderMarkers(data);
});
function prepareToCompare(str) {
return convertSpecialsCharacters(str.toString().toLowerCase().trim())
}
function convertSpecialsCharacters(str) {
const conversions = {}
conversions.a = 'ą'
conversions.e = 'ę'
conversions.z = 'ż|ź'
conversions.s = 'ś'
conversions.c = 'ć'
conversions.l = 'ł'
conversions.o = 'ó|ò'
conversions.A = 'Ą'
conversions.E = 'Ę'
conversions.Z = 'Ż|Ź'
conversions.S = 'Ś'
conversions.C = 'Ć'
conversions.L = 'Ł'
conversions.O = 'Ó|Ò'
for (const i in conversions) {
const re = new RegExp(conversions[i], 'g')
str = str.replace(re, i)
}
return str
}
// ---------------- MAP ----------------
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
zoom: 6,
center: { lat: 52.1, lng: 19.4 },
mapTypeId: 'roadmap'
});
}
function renderMarkers(data) {
data.forEach(place => {
place.shops.forEach(shop => {
if (!shop.lat || !shop.lng) return;
const marker = new google.maps.Marker({
map,
icon: iconB,
position: { lat: +shop.lat, lng: +shop.lng }
});
marker.addListener('click', () => {
map.setZoom(16);
map.panTo(marker.getPosition());
filterList(place.name);
});
markers.push(marker);
});
});
}
// ---------------- LIST ----------------
function renderList(data) {
const list = document.getElementById('shopList');
list.querySelectorAll('.place:not(.forCloning)').forEach(el => el.remove());
let count = 0;
data.forEach(place => {
place.shops.forEach(shop => {
const clone = list.querySelector('.forCloning').cloneNode(true);
clone.classList.remove('forCloning');
clone.querySelector('.lat').textContent = shop.lat;
clone.querySelector('.lng').textContent = shop.lng;
clone.querySelector('.shopAddress').textContent =
`${place.name} - ${shop.address || ''}`;
clone.querySelector('.shopHours').textContent =
shop.open_hours || '—';
clone.querySelector('.link').href = shop.map_url || '#';
clone.addEventListener('click', (e) => {
map.setZoom(16);
map.panTo({ lat: +shop.lat, lng: +shop.lng });
const ancestor = e.target.closest('.place')
ancestor.classList.add('active')
ancestor.getElementsByTagName('INPUT')[0].checked = true
});
list.appendChild(clone);
count++;
});
});
document.querySelector('.dynamic').textContent = count;
document.querySelector('.resultQ').classList.add('active');
}
// ---------------- SEARCH ----------------
function buildSuggestList() {
const ul = document.querySelector('.suggestList');
places.forEach(p => {
const li = document.createElement('li');
li.textContent = p.name;
li.onclick = () => {
document.getElementById('tinp').value = p.name;
filterList(p.name);
};
ul.appendChild(li);
});
}
function filterList(val) {
renderList(
places.filter(p =>
p.name.toLowerCase().includes(val.toLowerCase())
)
);
}
function filterAvailablePlaces(actualVal) {
let listElements = document.querySelectorAll('.suggestList li')
for (const element of listElements) {
if (
!prepareToCompare(element.innerText).includes(
prepareToCompare(actualVal)
)
) {
element.style.display = 'none'
} else {
element.style.display = 'block'
}
}
}
const searchInp = document.getElementById('tinp')
searchInp.onfocus = function (e) {
e.target.closest('.searchCont').classList.add('active')
}
searchInp.addEventListener('focusout', function (e) {
let targ = e.target
setTimeout(function () {
targ.closest('.searchCont').classList.remove('active')
}, 200)
})
searchInp.onkeyup = function (e) {
if (e.keyCode == 13) {
document.getElementById('search').click()
} else {
filterAvailablePlaces(searchInp.value)
}
}
document.getElementById('search').onclick = () => {
filterList(document.getElementById('tinp').value);
};
// ---------------- STYLES ----------------
function injectStyles() {
const style = document.createElement('style');
style.innerHTML = `
#salony-widget *{
margin: 0;
padding: 0;
box-sizing: border-box;
}
#salony-widget{
min-height: 40vw;
display: flex;
flex-direction: row;
}
#salony-widget .mapFull {
flex-basis:65%;
min-height:40vw;
}
#salony-widget .mapFull #map{
filter: grayscale(100%);
height: 100%;
width: 100%;
}
#salony-widget .mapResults {
padding: 0 16px 0 48px;
flex-grow: 0;
flex-shrink: 0;
flex-basis: 35%;
}
#salony-widget .mapResults h1 {
color: #333;
font-family: 'Raleway', sans-serif;
font-size: 29px;
font-weight: 800;
line-height: 1.3;
margin: 0;
}
#salony-widget .mapResults .searchCont {
margin: 18px 0;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
max-width: 340px;
border: 2px solid rgba(0, 0, 0, .7);
}
#salony-widget .mapResults .searchCont input{
font-family: 'Raleway', sans-serif;
border: none;
font-size: 14px;
outline: none;
padding: 10px;
color: #333;
width: calc(100% - 40px);
height: 40px;
box-shadow: none;
border-radius: 0;
}
#salony-widget .mapResults .searchCont label{
font-family: 'Raleway', sans-serif;
font-size: 12px;
font-weight: 800;
position: absolute;
color: rgba(0, 0, 0, .6);
top: -10px;
padding: 2px;
background: #fff;
left: 4px;
transform: scale(0);
opacity: 0;
transition: 0.5s;
}
#salony-widget .mapResults .searchCont button{
cursor: pointer;
border: none;
width: 40px;
height: 40px;
outline: none;
display: inline-flex;
align-items: center;
justify-content: center;
background: #fff;
}
#salony-widget .mapResults .searchCont .suggestList {
position: absolute;
box-shadow: 2px 3px 10px rgba(0, 0, 0, .3);
top: 20px;
border-radius: 3px;
padding: 0 !important;
max-height: 100px;
z-index: 10;
overflow: auto;
width: 100%;
background: #fff;
display: none;
margin: 20px 0;
list-style-type: none;
}
#salony-widget .mapResults .searchCont.active .suggestList {
display: block;
}
#salony-widget .mapResults .searchCont .suggestList li {
font-family: 'Raleway', sans-serif;
font-size: 13px;
cursor: pointer;
padding: 5px;
transition: 0.3s;
align-items: flex-start;
}
#salony-widget .mapResults .searchCont .suggestList li:hover {
background: #f2f2f2;
}
#salony-widget .mapResults .searchCont .suggestList li.firstSuggest {
padding: 0px;
}
#salony-widget .mapResults .searchCont label {
font-family: 'Raleway', sans-serif;
font-size: 12px;
font-weight: 800;
position: absolute;
color: rgba(0, 0, 0, .6);
top: -10px;
padding: 2px;
background: #fff;
left: 4px;
transform: scale(0);
opacity: 0;
transition: 0.5s;
}
#salony-widget .mapResults .searchCont.active label {
transform: scale(1);
opacity: 1;
}
#salony-widget .mapResults .resultQ {
visibility: hidden;
color: #333;
font-family: 'Raleway', sans-serif;
font-weight: 300;
font-size: 14px;
}
#salony-widget .mapResults .resultQ.active {
visibility: visible;
}
#salony-widget .mapResults #shopList {
padding: 0 5px;
margin: 20px 0;
list-style-type: none;
max-height: 360px;
overflow: auto;
}
#salony-widget .mapResults #shopList li.place.forCloning {
display: none;
}
#salony-widget .mapResults #shopList li.place {
display: flex;
align-items: flex-start;
padding: 10px 0;
cursor: pointer;
}
#salony-widget .mapResults #shopList li.place a {
font-family: 'Raleway', sans-serif;
margin-top: 8px;
display: none;
font-size: 13px;
color: #000;
text-decoration: none;
}
#salony-widget .mapResults #shopList li.place.active a {
display: block;
}
#salony-widget .mapResults #shopList li.place input{
margin-right: 10px;
position: relative;
top: 6px;
color: #000;
}
#salony-widget .mapResults #shopList li.place .shopHiddenInfo{
display: none;
}
#salony-widget .mapResults #shopList li.place .content h2{
color: #333;
line-height: 1.3;
font-family: 'Raleway', sans-serif;
font-weight: 500;
font-size: 14px;
}
#salony-widget .mapResults #shopList li.place .content p{
color: #333;
font-family: 'Raleway', sans-serif;
line-height: 1.3;
font-weight: 300;
font-size: 14px;
}
`;
document.head.appendChild(style);
}
})();