Dodanie nowej strony admina co pozwala dodawać pane kontaktowe

This commit is contained in:
Roman Pyrih
2026-01-27 15:34:07 +01:00
parent ff58ac80ef
commit 92248df9d1
11 changed files with 692 additions and 0 deletions

View File

@@ -0,0 +1,262 @@
<script type="text/javascript" src="/libraries/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="/libraries/ckeditor/adapters/jquery.js"></script>
<script type="text/javascript" src="/libraries/jquery/sortable/sortable.js"></script>
<?php
global $gdb;
/** @var array|null $item */
/** @var array $products */
/** @var array $provinces */
$item = $this -> item ?? [];
$products = $this -> products ?? [];
$provinces = $this -> provinces ?? [];
/** Województwa */
$province_values = [ 0 => '--- wybierz województwo ---' ];
if (is_array($provinces)) {
foreach ($provinces as $p) {
$province_values[(int)$p['id']] = $p['name'];
}
}
/** Typ salonu */
$salon_type_values = [
'sales' => 'SALON SPRZEDAŻY',
'partner' => 'SALON PARTNERSKI',
];
/** Produkty */
$selected_products = $item['products'] ?? [];
if (!is_array($selected_products)) $selected_products = [];
ob_start();
?>
<div id="settings-tabs">
<ul class="resp-tabs-list settings-tabs">
<li><i class="fa fa-map-marker"></i>Dane salonu</li>
<li><i class="fa fa-phone"></i>Kontakt</li>
<li><i class="fa fa-cubes"></i>Produkty</li>
<li><i class="fa fa-wrench"></i>Ustawienia</li>
</ul>
<div class="resp-tabs-container settings-tabs">
<!-- TAB 1: Dane salonu -->
<div>
<?= \Html::select([
'label' => 'Województwo',
'name' => 'province_id',
'id' => 'province_id',
'values' => $province_values,
'value' => (int)($item['province_id'] ?? 0),
]);?>
<?= \Html::select([
'label' => 'Typ salonu',
'name' => 'salon_type',
'id' => 'salon_type',
'values' => $salon_type_values,
'value' => ($item['salon_type'] ?? 'sales'),
]);?>
<?= \Html::input([
'label' => 'Nazwa salonu',
'name' => 'salon_name',
'id' => 'salon_name',
'value' => ($item['salon_name'] ?? ''),
'inline' => true
]);?>
<?= \Html::input([
'label' => 'Miasto',
'name' => 'city',
'id' => 'city',
'value' => ($item['city'] ?? ''),
'inline' => true
]);?>
<?= \Html::textarea([
'label' => 'Adres',
'name' => 'address',
'id' => 'address',
'value' => ($item['address'] ?? ''),
'inline' => true
]);?>
<?= \Html::input([
'label' => 'Koordynaty (lat, lng)',
'name' => 'coords',
'id' => 'coords',
'value' => ($item['coords'] ?? ''),
'inline' => true,
'desc' => 'Np. 50.07576365941408, 21.973886126521034'
]);?>
<?= \Html::textarea([
'label' => 'Godziny otwarcia',
'name' => 'opening_hours',
'id' => 'opening_hours',
'value' => ($item['opening_hours'] ?? ''),
'inline' => true,
'desc' => 'Np. Pn-Pt: 8-17 | Sob: 9-13 | Nd: nieczynne'
]);?>
<?= \Html::input_icon([
'label' => 'Baner (obrazek salonu)',
'name' => 'banner_image',
'id' => 'banner_image',
'value' => ($item['banner_image'] ?? ''),
'icon_content' => 'przeglądaj',
'inline' => true,
'icon_js' => "window.open('/libraries/filemanager-9.14.1/dialog.php?type=1&popup=1&field_id=banner_image&akey=c3cb2537d25c0efc9e573d059d79c3b8','mywindow','location=1,status=1,scrollbars=1,width=1100,height=700');"
]);?>
</div>
<!-- TAB 2: Kontakt -->
<div>
<?php
$phones_txt = '';
if (!empty($item['phones']) && is_array($item['phones'])) $phones_txt = implode("\n", $item['phones']);
$emails_txt = '';
if (!empty($item['emails']) && is_array($item['emails'])) $emails_txt = implode("\n", $item['emails']);
?>
<?= \Html::textarea([
'label' => 'Telefony (1 numer = 1 linia)',
'name' => 'phones',
'id' => 'phones',
'value' => $phones_txt,
'inline' => true
]);?>
<?= \Html::textarea([
'label' => 'E-maile (1 email = 1 linia)',
'name' => 'emails',
'id' => 'emails',
'value' => $emails_txt,
'inline' => true
]);?>
<?= \Html::input([
'label' => 'Tekst przycisku',
'name' => 'button_label',
'id' => 'button_label',
'value' => ($item['button_label'] ?? 'SKONTAKTUJ SIĘ Z NAMI'),
'inline' => true
]);?>
<?= \Html::input([
'label' => 'Link przycisku (URL)',
'name' => 'button_url',
'id' => 'button_url',
'value' => ($item['button_url'] ?? ''),
'inline' => true,
'desc' => 'Np. https://twojadomena.pl/kontakt'
]);?>
</div>
<!-- TAB 3: Produkty -->
<div>
<div class="title">Dostępne produkty</div>
<?php if (is_array($products) && count($products)): ?>
<div style="display:flex; flex-wrap:wrap; gap:14px;">
<?php foreach ($products as $p): ?>
<?php
$pid = (int)$p['id'];
$checked = in_array($pid, $selected_products) ? 'checked' : '';
?>
<label style="display:flex; align-items:center; gap:20px; padding:10px 12px; border:1px solid #ddd; border-radius:6px; cursor:pointer;">
<input type="checkbox" name="products[]" value="<?= $pid; ?>" <?= $checked; ?> />
<div style="display:flex; flex-direction:column; align-items:center; gap:10px;">
<span style="text-align: center;"><?= $p['name']; ?></span>
<img src="/<?= ltrim($p['icon'], '/'); ?>" alt="" style="width:36px; height:36px; object-fit:contain;" />
</div>
</label>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="alert alert-warning">Brak produktów w pp_contacts_maps_products.</div>
<?php endif; ?>
</div>
<!-- TAB 4: Ustawienia -->
<div>
<?= \Html::input_switch([
'label' => 'Aktywny',
'name' => 'is_active',
'checked' => (!isset($item['is_active']) || (int)$item['is_active'] === 1) ? true : false
]);?>
<?= \Html::input([
'label' => 'Sortowanie',
'name' => 'sort',
'id' => 'sort',
'value' => (int)($item['sort'] ?? 0),
]);?>
</div>
<script type="text/javascript">
$( function() {
$( '#address, #opening_hours' ).ckeditor( {
toolbar : 'MyTool',
height:'200',
});
});
</script>
</div>
</div>
<?php
$out = ob_get_clean();
$grid = new \gridEdit;
$grid -> id = 'contacts-maps-edit';
$grid -> gdb_opt = $gdb;
$grid -> include_plugins = true;
$grid -> title = 'Edycja autora';
$grid -> title = $id ? 'Edycja salonu' : 'Dodaj salon';
$grid->fields = [
[
'db' => 'id',
'type' => 'hidden',
'value' => $this->item['id']
]
];
$grid -> external_code = $out;
$grid -> actions = [
'save' => [ 'url' => '/admin/contacts_maps/save/', 'back_url' => '/admin/contacts_maps/view_list/' ],
'cancel' => [ 'url' => '/admin/contacts_maps/view_list/' ]
];
$grid -> persist_edit = true;
$grid -> id_param = 'id';
echo $grid -> draw();
?>
<script type="text/javascript">
$(function ()
{
disable_menu();
$( '#settings-tabs' ).easyResponsiveTabs({
width: 'auto',
fit: true,
tabidentify: 'settings-tabs',
type: 'vertical'
});
$('#languages-main').easyResponsiveTabs({
width: 'auto',
fit: true,
tabidentify: 'languages-main'
});
});
</script>
<script>CKEDITOR.dtd.$removeEmpty['span'] = false;</script>

View File

@@ -0,0 +1,45 @@
<?php
global $gdb;
$grid = new \grid('pp_contacts_maps');
$grid->gdb_opt = $gdb;
$grid->columns_view = [
[
'name' => 'Lp.',
'autoincrement' => true
],
[
'name' => 'Salon',
'db' => 'salon_name',
'php' => 'echo "<a href=\"/admin/contacts_maps/edit/id=[id]\">[salon_name]</a>";'
],
[
'name' => 'Miasto',
'db' => 'city'
],
[
'name' => 'Typ',
'db' => 'salon_type',
'php' => 'echo ([salon_type] === "sales" ? "SALON SPRZEDAŻY" : ([salon_type] === "partner" ? "SALON PARTNERSKI" : ""));'
],
[
'name' => 'Aktywny',
'db' => 'is_active',
'php' => 'echo ([is_active] ? "TAK" : "NIE");'
],
[
'name' => 'Usuń',
'action' => [ 'type' => 'delete', 'url' => '/admin/contacts_maps/delete/id=[id]' ]
]
];
$grid->buttons = [
[
'label' => 'Dodaj salon',
'url' => '/admin/contacts_maps/edit/',
'class' => 'btn-success'
]
];
echo $grid->draw();

View File

@@ -88,6 +88,11 @@
<a href="/admin/scontainers/view_list/"><img src="/admin/css/icons/container-solid.svg">Zawartość statyczna</a>
<? }?>
</li>
<? if ( \admin\factory\Users::check_privileges( 'scontainers_administration', $user[ 'id' ] ) ): ?>
<li>
<a href="/admin/contacts_maps/view_list/"><img src="/admin/css/icons/list-settings-line.svg">Kontakt - lista salonów</a>
</li>
<? endif; ?>
</ul>
<?
if ( \admin\factory\Users::check_privileges( 'fileManager_administration', $user[ 'id' ] ) || \admin\factory\Users::check_privileges( 'baners_administration', $user[ 'id' ] ) || \admin\factory\Users::check_privileges( 'emails_administration', $user[ 'id' ] ) )

30
api/contact_map.php Normal file
View File

@@ -0,0 +1,30 @@
<?php
error_reporting( E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING ^ E_DEPRECATED );
function __autoload_my_classes( $classname )
{
$q = explode( '\\' , $classname );
$c = array_pop( $q );
$f = '../autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
if ( file_exists( $f ) )
require_once( $f );
}
spl_autoload_register( '__autoload_my_classes' );
date_default_timezone_set( 'Europe/Warsaw' );
require_once '../config.php';
require_once '../libraries/medoo/medoo.php';
session_start();
$mdb = new medoo( [
'database_type' => 'mysql',
'database_name' => $database['name'],
'server' => $database['host'],
'username' => $database['user'],
'password' => $database['password'],
'charset' => 'utf8'
] );
\front\controls\ContactsMapsApi::index();
?>

View File

@@ -0,0 +1,58 @@
<?
namespace admin\controls;
class ContactsMaps
{
static public function view_list()
{
global $user;
if ( !\admin\factory\Users::check_privileges( 'scontainers_administration', $user['id'] ) )
return \S::alert( 'Nie masz uprawnień' );
return \Tpl::view( 'contacts-maps/view-list' );
}
static public function edit()
{
global $user;
if (!\admin\factory\Users::check_privileges('scontainers_administration', $user['id']))
return \S::alert('Nie masz uprawnień');
return \Tpl::view('contacts-maps/element-edit', [
'item' => \admin\factory\ContactsMaps::get(\S::get('id')),
'products' => \admin\factory\ContactsMaps::products(),
'provinces' => \admin\factory\ContactsMaps::provinces(),
]);
}
static public function save()
{
global $user;
if (!\admin\factory\Users::check_privileges('scontainers_administration', $user['id']))
return;
$values = \S::json_to_array(\S::get('values'));
$id = \admin\factory\ContactsMaps::save($values);
echo json_encode([
'status' => $id ? 'ok' : 'error',
'id' => $id
]);
exit;
}
static public function delete()
{
global $user;
if (!\admin\factory\Users::check_privileges('scontainers_administration', $user['id']))
return;
\admin\factory\ContactsMaps::delete(\S::get('id'));
header('Location: /admin/contacts_maps/view_list/');
exit;
}
}

View File

@@ -0,0 +1,137 @@
<?
namespace admin\factory;
class ContactsMaps
{
static public function products()
{
global $mdb;
return $mdb->select(
'pp_contacts_maps_products',
[ 'id', 'code', 'name', 'icon' ],
[
'is_active' => 1,
'ORDER' => [
'sort' => 'ASC'
]
]
);
}
static public function provinces()
{
global $mdb;
return $mdb->select(
'pp_contacts_maps_provinces',
[ 'id', 'code', 'name'],
[
'ORDER' => [
'id' => 'ASC'
]
]
);
}
static public function get($id)
{
global $mdb;
$id = (int)$id;
if (!$id) return null;
$row = $mdb->get(
'pp_contacts_maps',
'*',
[ 'id' => $id ]
);
if (!$row) return null;
$row['phones'] = json_decode($row['phones_json'], true) ?: [];
$row['emails'] = json_decode($row['emails_json'], true) ?: [];
$row['products'] = json_decode($row['products_json'], true) ?: [];
$row['coords'] = '';
if (!empty($row['lat']) && !empty($row['lng'])) {
$row['coords'] = $row['lat'] . ', ' . $row['lng'];
}
return $row;
}
static public function save($v)
{
global $mdb;
$id = (int)($v['id'] ?? 0);
// coords: "lat, lng"
$coords = trim($v['coords'] ?? '');
$lat = 0; $lng = 0;
if ($coords) {
$parts = array_map('trim', explode(',', $coords));
if (count($parts) >= 2) {
$lat = (float)str_replace(' ', '', $parts[0]);
$lng = (float)str_replace(' ', '', $parts[1]);
}
}
$phones = array_filter(array_map('trim', explode("\n", $v['phones'] ?? '')));
$emails = array_filter(array_map('trim', explode("\n", $v['emails'] ?? '')));
$products = [];
if (isset($v['products']) && is_array($v['products'])) {
$products = $v['products'];
}
elseif (isset($v['products[]']) && is_array($v['products[]'])) {
$products = $v['products[]'];
}
$data = [
'province_id' => (int)($v['province_id'] ?? 0),
'city' => trim($v['city'] ?? ''),
'salon_type' => $v['salon_type'] ?? 'sales',
'salon_name' => trim($v['salon_name'] ?? ''),
'address' => trim($v['address'] ?? ''),
'lat' => $lat,
'lng' => $lng,
'opening_hours' => trim($v['opening_hours'] ?? ''),
'phones_json' => json_encode($phones, JSON_UNESCAPED_UNICODE),
'emails_json' => json_encode($emails, JSON_UNESCAPED_UNICODE),
'products_json' => json_encode(array_map('intval', $products)),
'button_label' => trim($v['button_label'] ?? 'SKONTAKTUJ SIĘ Z NAMI'),
'button_url' => trim($v['button_url'] ?? ''),
'banner_image' => trim($v['banner_image'] ?? ''),
'is_active' => !empty($v['is_active']) ? 1 : 0,
'sort' => (int)($v['sort'] ?? 0),
];
if ($id) {
$mdb->update('pp_contacts_maps', $data, [ 'id' => $id ]);
return $id;
}
$mdb->insert('pp_contacts_maps', $data);
return (int)$mdb->id();
}
static public function delete($id)
{
global $mdb;
$id = (int)$id;
if (!$id) return false;
return $mdb->delete(
'pp_contacts_maps',
[ 'id' => $id ]
);
}
}

View File

@@ -0,0 +1,24 @@
<?
namespace front\controls;
class ContactsMapsApi
{
static public function index()
{
// JSON header
header('Content-Type: application/json; charset=utf-8');
$only_active = true;
if (\S::get('all') == 1) $only_active = false; // przykład ?all=1
$data = \front\factory\ContactsMaps::api_list($only_active);
echo json_encode([
'status' => 'ok',
'count' => count($data),
'data' => $data,
], JSON_UNESCAPED_UNICODE);
exit;
}
}

View File

@@ -0,0 +1,115 @@
<?
namespace front\factory;
class ContactsMaps
{
static public function api_list($only_active = true)
{
global $mdb;
// 1) забираємо довідники і індексуємо по id
$provinces = $mdb->select(
'pp_contacts_maps_provinces',
[ 'id', 'code', 'name' ],
[ 'ORDER' => [ 'id' => 'ASC' ] ]
);
$provinces_by_id = [];
foreach ($provinces as $p) {
$provinces_by_id[(int)$p['id']] = $p;
}
$products = $mdb->select(
'pp_contacts_maps_products',
[ 'id', 'code', 'name', 'icon' ],
[
'is_active' => 1,
'ORDER' => [ 'sort' => 'ASC' ]
]
);
$products_by_id = [];
foreach ($products as $pr) {
$products_by_id[(int)$pr['id']] = $pr;
}
// 2) забираємо салони
$where = [
'ORDER' => [ 'sort' => 'ASC' ]
];
if ($only_active) $where['is_active'] = 1;
$rows = $mdb->select('pp_contacts_maps', '*', $where);
// 3) збираємо API формат
$out = [];
foreach ($rows as $r)
{
$phones = json_decode($r['phones_json'] ?? '[]', true);
if (!is_array($phones)) $phones = [];
$emails = json_decode($r['emails_json'] ?? '[]', true);
if (!is_array($emails)) $emails = [];
$prod_ids = json_decode($r['products_json'] ?? '[]', true);
if (!is_array($prod_ids)) $prod_ids = [];
$prod_out = [];
foreach ($prod_ids as $pid) {
$pid = (int)$pid;
if (isset($products_by_id[$pid])) {
$prod_out[] = [
'id' => (int)$products_by_id[$pid]['id'],
'code' => $products_by_id[$pid]['code'],
'name' => $products_by_id[$pid]['name'],
'icon' => $products_by_id[$pid]['icon'],
];
}
}
$prov = $provinces_by_id[(int)$r['province_id']] ?? null;
$out[] = [
'id' => (int)$r['id'],
'province' => $prov ? [
'id' => (int)$prov['id'],
'code' => $prov['code'],
'name' => $prov['name'],
] : null,
'city' => $r['city'],
'salon_type' => $r['salon_type'],
'salon_name' => $r['salon_name'],
'address' => $r['address'],
'position' => [
'lat' => (float)$r['lat'],
'lng' => (float)$r['lng'],
],
'opening_hours' => $r['opening_hours'] ?? '',
'contact' => [
'phones' => array_values($phones),
'emails' => array_values($emails),
],
'products' => $prod_out,
'button' => [
'label' => $r['button_label'] ?? '',
'url' => $r['button_url'] ?? '',
],
'banner_image' => $r['banner_image'] ?? '',
'is_active' => (int)$r['is_active'],
'sort' => (int)$r['sort'],
'updated_at' => $r['updated_at'] ?? null,
];
}
return $out;
}
}

View File

@@ -0,0 +1,5 @@
<svg width="34" height="29" viewBox="0 0 34 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.21484 26.3117L16.6101 26.3326L16.6279 16.2073L16.2782 16.0043C15.8811 15.7744 15.4841 15.1264 15.487 14.1948C15.487 13.4005 15.813 12.66 16.2812 12.3913L16.6338 12.1882L16.6427 2.05996L2.25633 2.03906L2.21484 26.3087V26.3117ZM3.64903 3.44842L15.247 3.46634L15.2411 11.4179C14.5329 12.018 14.0914 13.0572 14.0914 14.1948C14.0884 15.3294 14.524 16.3715 15.2322 16.9777L15.2174 24.9292L3.61348 24.9113L3.652 3.45439L3.64903 3.44842Z" fill="#1E2832"/>
<path d="M17.1204 12.1848L17.4701 12.3879C17.9383 12.6596 18.2642 13.4031 18.2613 14.1973C18.2613 15.123 17.8494 15.7829 17.4671 16.0008L17.1175 16.2039L17.0938 26.3292L31.489 26.356L31.5216 2.0834L17.1323 2.0625L17.1145 12.1848H17.1204ZM18.5131 16.9742C19.2184 16.3741 19.6569 15.332 19.6569 14.2003C19.6599 13.0627 19.2243 12.0206 18.5161 11.4174L18.5309 3.47186L30.1289 3.48978L30.0992 24.9467L18.4953 24.9258L18.5131 16.9742Z" fill="#1E2832"/>
<path d="M0 28.3425L33.7124 28.3932L33.7509 0.0507609L0.0444481 0L0 28.3425ZM1.44012 1.40936L32.3523 1.45714L32.3197 26.9869L1.39863 26.9391L1.44012 1.40936Z" fill="#1E2832"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,4 @@
<svg width="31" height="38" viewBox="0 0 31 38" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M26.9423 34.3686L26.9887 0.0306937L2.91691 0L2.87052 34.3686H0V37.2594H30.1955V34.3686H26.9423ZM5.96429 34.3686V26.1204L6.01069 3.10286V2.97729L23.9529 3.00241L23.9094 26.1483V34.3714H5.96429V34.3686ZM4.27968 1.31704L25.6201 1.34494L25.5766 34.3686H25.275V26.1483L25.3185 3.34562L25.3243 1.69095L4.64502 1.66304L4.59863 26.1204V34.3379H4.23329L4.27678 1.31704H4.27968Z" fill="#1E2832"/>
<path d="M20.2605 16.3594C19.7299 16.3594 19.3008 16.7723 19.3008 17.283C19.3008 17.7936 19.7299 18.2066 20.2605 18.2066C20.7911 18.2066 21.2203 17.7936 21.2203 17.283C21.2203 16.7723 20.7911 16.3594 20.2605 16.3594Z" fill="#1E2832"/>
</svg>

After

Width:  |  Height:  |  Size: 734 B

View File

@@ -0,0 +1,7 @@
<svg width="34" height="31" viewBox="0 0 34 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M32.0376 0.0464607L1.80016 0L1.75668 26.108H0.00579762L0 30.1153L33.7421 30.1675L33.7479 26.1603H31.997L32.0347 0.0493645L32.0376 0.0464607ZM32.3826 27.5222V28.7969L2.435 28.7476H1.36824V27.4757L3.12202 27.4786L3.1655 1.37059L30.6723 1.41125L30.6346 27.5193H32.3855L32.3826 27.5222Z" fill="#1E2832"/>
<path d="M29.0056 3.46875H4.77734V4.87128H29.0056V3.46875Z" fill="#1E2832"/>
<path d="M29.0056 13.9609H4.77734V15.3635H29.0056V13.9609Z" fill="#1E2832"/>
<path d="M29.0056 8.71094H4.77734V10.1135H29.0056V8.71094Z" fill="#1E2832"/>
<path d="M4.09375 26.4581H29.6873V17.7118L4.09375 17.7031V26.4552V26.4581ZM5.45909 19.0737L28.322 19.0795V25.0904H5.45909V19.0737Z" fill="#1E2832"/>
</svg>

After

Width:  |  Height:  |  Size: 793 B