[add] главный экран

This commit is contained in:
Петрищев Алексей 2025-11-26 18:41:43 +03:00
parent 70e6f304bb
commit 6a1a0e001c
19 changed files with 362 additions and 181 deletions

View File

@ -0,0 +1,3 @@
#!/bin/bash
cd /var/www/html/engine/cron/data_sync || exit 1
php sync_doctors_and_zones.php

View File

@ -10,6 +10,7 @@ $DRY_RUN = false; // ← поставить true для тестового за
// Источник (старая МИС) // Источник (старая МИС)
$src = new db(); $src = new db();
// $src->connect("192.168.111.234", "dbuser", "dbpassword", "pnd8");
$src->connect("192.168.0.2", "root", "Xx29Pz33", "pnd8"); $src->connect("192.168.0.2", "root", "Xx29Pz33", "pnd8");
// Приёмник (новая база) // Приёмник (новая база)
@ -145,11 +146,11 @@ function replaceTables($dst, $DRY_RUN)
replaceTables($dst, $DRY_RUN); replaceTables($dst, $DRY_RUN);
function getDoctorSchedule($src, $doctorId) { function getDoctorSchedule($src, $doctorId)
{
// Генерируем диапазон дат: -3 дня назад и +4 вперёд // диапазон на 10 дней включая сегодня
$startDate = date('Y-m-d', strtotime('-3 days')); $startDate = date('Y-m-d');
$endDate = date('Y-m-d', strtotime('+4 days')); $endDate = date('Y-m-d', strtotime('+9 days'));
$sql = " $sql = "
SELECT SELECT
@ -174,45 +175,34 @@ function getDoctorSchedule($src, $doctorId) {
GROUP BY DATE(e.execDate) GROUP BY DATE(e.execDate)
"; ";
$rows = $src->q($sql)->fetchAll(); $rows = $src->q($sql)->fetchAll();
// Подготовка недели: day1..day7 = Пн..Вс $result = [];
$week = [
1 => 'day1',
2 => 'day2',
3 => 'day3',
4 => 'day4',
5 => 'day5',
6 => 'day6',
7 => 'day7',
];
// Заполняем пустыми днями
$result = [
'day1' => null,
'day2' => null,
'day3' => null,
'day4' => null,
'day5' => null,
'day6' => null,
'day7' => null,
];
// 1. Сначала добавляем ВСЕ даты из SQL
foreach ($rows as $r) { foreach ($rows as $r) {
$day = trim($r['day']); // защита от пробелов
// 1 воскресенье, нам нужно 1=Пн → 7=Вс $result[$day] = [
$dayNum = date('N', strtotime($r['day']));
$key = $week[$dayNum];
$result[$key] = [
'start' => $r['work_start'] ?: null, 'start' => $r['work_start'] ?: null,
'end' => $r['work_end'] ?: null, 'end' => $r['work_end'] ?: null,
]; ];
} }
// 2. Потом добавляем пустые даты вперёд на 10 дней
for ($i = 0; $i < 10; $i++) {
$date = date('Y-m-d', strtotime("+{$i} days"));
if (!array_key_exists($date, $result)) {
$result[$date] = null;
}
}
return json_encode($result, JSON_UNESCAPED_UNICODE); return json_encode($result, JSON_UNESCAPED_UNICODE);
} }
echo "\n--- DONE (DRY_RUN = " . ($DRY_RUN ? "YES" : "NO") . ") ---\n"; echo "\n--- DONE (DRY_RUN = " . ($DRY_RUN ? "YES" : "NO") . ") ---\n";

View File

@ -0,0 +1,2 @@
UPDATE `_sys_cat` SET `_sys_cat`.`module` = "site_screensaver.php" , `_sys_cat`.`data` = "" WHERE `_sys_cat`.`cat_id` = "index";
UPDATE `_sys_cat` SET `_sys_cat`.`xml` = "index.xml" WHERE `_sys_cat`.`cat_id` = "--btns-";

View File

@ -0,0 +1,4 @@
UPDATE `_sys_cat` SET `_sys_cat`.`xml` = "empty.xml" WHERE `_sys_cat`.`cat_id` = "index";
UPDATE `_sys_cat` SET `_sys_cat`.`_sys_deleted` = "1" WHERE `_sys_cat`.`cat_id` = "core-menuconfig";
UPDATE `_sys_cat` SET `_sys_cat`.`_sys_deleted` = "1" WHERE `_sys_cat`.`cat_id` = "-no-auth-links-";
;

View File

@ -3,8 +3,8 @@
</div> </div>
<div class="card-body text-center"> <div class="card-body text-center">
<p class="mb-3"> <p class="mb-3">
Не удалось определить участок. <br> Попробуйте ввести другой адрес <br>
Попробуйте ввести другой адрес. или обратитесь в регистратуру
</p> </p>
</div> </div>

View File

@ -0,0 +1,83 @@
<style>
html, body { height:100%; }
/* Плавное появление контента */
.fade-in {
opacity: 0;
animation: fadeIn 1s ease-in-out forwards;
animation-delay: .4s;
}
@keyframes fadeIn {
to { opacity: 1; }
}
/* Прелоадер */
#loader {
position: fixed;
left: 0; top: 0;
width: 100%; height: 100%;
background: #d9e1f2;
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.spinner {
width: 60px;
height: 60px;
border: 6px solid #fff;
border-top-color: #4a6ea9;
border-radius: 50%;
animation: spin 0.9s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
<!-- Прелоадер -->
<div id="loader">
<div class="spinner"></div>
</div>
<!-- Основной экран -->
<a href="?cat=main" style="text-decoration:none; color:inherit;">
<div class="d-flex justify-content-center align-items-center fade-in"
style="min-height:100vh; background:#d9e1f2; cursor:pointer;">
<div class="text-center px-3">
<!-- ЛОГОТИП -->
<img src="/assets/img/logo.png" alt="Логотип" style="height:110px;" class="mb-4">
<!-- НАЗВАНИЕ УЧРЕЖДЕНИЯ -->
<div style="font-size:32px; font-weight:700; line-height:1.2; color:#000;">
Психоневрологический<br>
диспансер №8
</div>
<!-- Заголовок страницы -->
<h1 class="mt-4" style="font-size:42px; font-weight:600; color:#000;">
Расписание приема врачей<br>и участков
</h1>
<div class="mt-3" style="font-size:20px; color:#333;">
(поиск по адресу проживания)
</div>
</div>
</div>
</a>
<script>
// Скрываем прелоадер после загрузки страницы
window.addEventListener('load', function() {
document.getElementById('loader').style.opacity = '0';
setTimeout(() => {
document.getElementById('loader').style.display = 'none';
}, 400);
});
</script>

View File

@ -1,18 +1,14 @@
<div class="text-center mb-4"> <div class="text-center mb-4">
<h2>Ваш участок обслуживания</h2> <h2 class="font-weight-bold">Ваш участок обслуживания</h2>
<h2 class="font-weight-bold"><?php echo \htmlentities($zone_name, ENT_QUOTES, 'UTF-8', false); ?></h2> <h2 class="font-weight-bold"><?php echo \htmlentities($zone_name, ENT_QUOTES, 'UTF-8', false); ?></h2>
</div> </div>
<?php
$today = date('N'); // 1..7
?>
<?php $__currentLoopData = $doctors; $this->addLoop($__currentLoopData);$this->getFirstLoop(); <?php $__currentLoopData = $doctors; $this->addLoop($__currentLoopData);$this->getFirstLoop();
foreach($__currentLoopData as $doc): $loop = $this->incrementLoopIndices(); ?> foreach($__currentLoopData as $doc): $loop = $this->incrementLoopIndices(); ?>
<div class="card mb-4 shadow-sm" style="border-radius:18px;"> <div class="card mb-4 shadow-sm" style="border-radius:18px;">
<div class="card-body"> <div class="card-body">
<div class="d-flex align-items-start mb-3">
<div class="d-flex align-items-start mb-3">
<?php /* Левая колонка с аватаром-вставкой по дизайну */ ?> <?php /* Левая колонка с аватаром-вставкой по дизайну */ ?>
<div style="font-size:20px;"> <div style="font-size:20px;">
<strong><?php echo \htmlentities($doc['name'], ENT_QUOTES, 'UTF-8', false); ?></strong><br> <strong><?php echo \htmlentities($doc['name'], ENT_QUOTES, 'UTF-8', false); ?></strong><br>
@ -35,38 +31,64 @@
</div> </div>
<?php <?php
$daysMap = [ // JSON формата: "2025-01-20" => {start:"", end:""} или null
'day1' => 'Понедельник', $schedule = json_decode($doc['schedule'] ?? '{}', true);
'day2' => 'Вторник',
'day3' => 'Среда',
'day4' => 'Четверг',
'day5' => 'Пятница',
'day6' => 'Суббота',
'day7' => 'Воскресенье',
];
$schedule = json_decode($doc['schedule'] ?? '', true); $todayDate = date('Y-m-d');
$filtered = [];
// Фильтруем только >= сегодня
foreach ($schedule as $date => $slot) {
if ($date >= $todayDate) {
$filtered[$date] = $slot;
}
}
// Для отображения дня недели
$ruDays = ['Вс','Пн','Вт','Ср','Чт','Пт','Сб'];
?> ?>
<hr class="mt-3 mb-3" style="border-color:#e5e5e5;"> <hr class="mt-3 mb-3" style="border-color:#e5e5e5;">
<h5 class="mb-3">Расписание приёма</h5> <h5 class="mb-3 font-weight-bold">Расписание приёма</h5>
<?php if(empty($schedule)): ?> <?php if(empty($filtered)): ?>
<div class="text-muted">Расписание не указано</div> <div class="text-muted">Расписание отсутствует</div>
<?php else: ?> <?php else: ?>
<ul class="list-unstyled mb-0"> <ul class="list-unstyled mb-0">
<?php $__currentLoopData = $daysMap; $this->addLoop($__currentLoopData);$this->getFirstLoop(); <?php $__currentLoopData = $filtered; $this->addLoop($__currentLoopData);$this->getFirstLoop();
foreach($__currentLoopData as $dayKey => $dayTitle): $loop = $this->incrementLoopIndices(); ?> foreach($__currentLoopData as $date => $slot): $loop = $this->incrementLoopIndices(); ?>
<?php <?php
$slot = $schedule[$dayKey] ?? null; $ts = strtotime($date);
$weekday = $ruDays[date('w', $ts)];
$months = [
1 => 'января',
2 => 'февраля',
3 => 'марта',
4 => 'апреля',
5 => 'мая',
6 => 'июня',
7 => 'июля',
8 => 'августа',
9 => 'сентября',
10 => 'октября',
11 => 'ноября',
12 => 'декабря',
];
$dayNum = (int)date('j', $ts); // 131 (без ведущего нуля)
$monthNum = (int)date('n', $ts); // 112
$monthName = $months[$monthNum];
$label = "{$dayNum} {$monthName} ({$weekday})";
?> ?>
<li class="d-flex justify-content-between py-2 border-bottom" <li class="d-flex justify-content-between py-2 border-bottom"
style="font-size:17px;"> style="font-size:17px;">
<span><?php echo \htmlentities($dayTitle, ENT_QUOTES, 'UTF-8', false); ?></span> <span class="font-weight-bold"><?php echo \htmlentities($label, ENT_QUOTES, 'UTF-8', false); ?></span>
<?php if($slot && !empty($slot['start']) && !empty($slot['end'])): ?> <?php if($slot && !empty($slot['start']) && !empty($slot['end'])): ?>
<span class="text-dark font-weight-bold"> <span class="text-dark font-weight-bold">
@ -86,4 +108,3 @@
</div> </div>
</div> </div>
<?php endforeach; $this->popLoop(); $loop = $this->getFirstLoop(); ?> <?php endforeach; $this->popLoop(); $loop = $this->getFirstLoop(); ?>

View File

@ -369,7 +369,7 @@
// Подменяем HTML на печатный // Подменяем HTML на печатный
document.body.innerHTML = document.body.innerHTML =
'<div style="padding:20px;">' + '<div style="width: 10cm; height: 10cm; page-break-after: always; font-weight: bold; font-size: 2em; ">' +
modalBody.innerHTML + modalBody.innerHTML +
'</div>'; '</div>';

View File

@ -1,17 +1,13 @@
<div class="text-center mb-4"> <div class="text-center mb-4">
<h2>Ваш участок обслуживания</h2> <h2 class="font-weight-bold">Ваш участок обслуживания</h2>
<h2 class="font-weight-bold">{{$zone_name}}</h2> <h2 class="font-weight-bold">{{$zone_name}}</h2>
</div> </div>
@php
$today = date('N'); // 1..7
@endphp
@foreach($doctors as $doc) @foreach($doctors as $doc)
<div class="card mb-4 shadow-sm" style="border-radius:18px;"> <div class="card mb-4 shadow-sm" style="border-radius:18px;">
<div class="card-body"> <div class="card-body">
<div class="d-flex align-items-start mb-3">
<div class="d-flex align-items-start mb-3">
{{-- Левая колонка с аватаром-вставкой по дизайну --}} {{-- Левая колонка с аватаром-вставкой по дизайну --}}
<div style="font-size:20px;"> <div style="font-size:20px;">
<strong>{{$doc['name']}}</strong><br> <strong>{{$doc['name']}}</strong><br>
@ -33,37 +29,63 @@
</div> </div>
@php @php
$daysMap = [ // JSON формата: "2025-01-20" => {start:"", end:""} или null
'day1' => 'Понедельник', $schedule = json_decode($doc['schedule'] ?? '{}', true);
'day2' => 'Вторник',
'day3' => 'Среда',
'day4' => 'Четверг',
'day5' => 'Пятница',
'day6' => 'Суббота',
'day7' => 'Воскресенье',
];
$schedule = json_decode($doc['schedule'] ?? '', true); $todayDate = date('Y-m-d');
$filtered = [];
// Фильтруем только >= сегодня
foreach ($schedule as $date => $slot) {
if ($date >= $todayDate) {
$filtered[$date] = $slot;
}
}
// Для отображения дня недели
$ruDays = ['Вс','Пн','Вт','Ср','Чт','Пт','Сб'];
@endphp @endphp
<hr class="mt-3 mb-3" style="border-color:#e5e5e5;"> <hr class="mt-3 mb-3" style="border-color:#e5e5e5;">
<h5 class="mb-3">Расписание приёма</h5> <h5 class="mb-3 font-weight-bold">Расписание приёма</h5>
@if(empty($schedule)) @if(empty($filtered))
<div class="text-muted">Расписание не указано</div> <div class="text-muted">Расписание отсутствует</div>
@else @else
<ul class="list-unstyled mb-0"> <ul class="list-unstyled mb-0">
@foreach($daysMap as $dayKey => $dayTitle) @foreach($filtered as $date => $slot)
@php @php
$slot = $schedule[$dayKey] ?? null; $ts = strtotime($date);
$weekday = $ruDays[date('w', $ts)];
$months = [
1 => 'января',
2 => 'февраля',
3 => 'марта',
4 => 'апреля',
5 => 'мая',
6 => 'июня',
7 => 'июля',
8 => 'августа',
9 => 'сентября',
10 => 'октября',
11 => 'ноября',
12 => 'декабря',
];
$dayNum = (int)date('j', $ts); // 131 (без ведущего нуля)
$monthNum = (int)date('n', $ts); // 112
$monthName = $months[$monthNum];
$label = "{$dayNum} {$monthName} ({$weekday})";
@endphp @endphp
<li class="d-flex justify-content-between py-2 border-bottom" <li class="d-flex justify-content-between py-2 border-bottom"
style="font-size:17px;"> style="font-size:17px;">
<span>{{ $dayTitle }}</span> <span class="font-weight-bold">{{ $label }}</span>
@if($slot && !empty($slot['start']) && !empty($slot['end'])) @if($slot && !empty($slot['start']) && !empty($slot['end']))
<span class="text-dark font-weight-bold"> <span class="text-dark font-weight-bold">
@ -82,4 +104,3 @@
</div> </div>
</div> </div>
@endforeach @endforeach

View File

@ -368,7 +368,7 @@
// Подменяем HTML на печатный // Подменяем HTML на печатный
document.body.innerHTML = document.body.innerHTML =
'<div style="padding:20px;">' + '<div style="width: 10cm; height: 10cm; page-break-after: always; font-weight: bold; font-size: 2em; ">' +
modalBody.innerHTML + modalBody.innerHTML +
'</div>'; '</div>';

View File

@ -3,8 +3,8 @@
</div> </div>
<div class="card-body text-center"> <div class="card-body text-center">
<p class="mb-3"> <p class="mb-3">
Не удалось определить участок. <br> Попробуйте ввести другой адрес <br>
Попробуйте ввести другой адрес. или обратитесь в регистратуру
</p> </p>
</div> </div>

View File

@ -0,0 +1,83 @@
<style>
html, body { height:100%; }
/* Плавное появление контента */
.fade-in {
opacity: 0;
animation: fadeIn 1s ease-in-out forwards;
animation-delay: .4s;
}
@keyframes fadeIn {
to { opacity: 1; }
}
/* Прелоадер */
#loader {
position: fixed;
left: 0; top: 0;
width: 100%; height: 100%;
background: #d9e1f2;
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.spinner {
width: 60px;
height: 60px;
border: 6px solid #fff;
border-top-color: #4a6ea9;
border-radius: 50%;
animation: spin 0.9s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
<!-- Прелоадер -->
<div id="loader">
<div class="spinner"></div>
</div>
<!-- Основной экран -->
<a href="?cat=main" style="text-decoration:none; color:inherit;">
<div class="d-flex justify-content-center align-items-center fade-in"
style="min-height:100vh; background:#d9e1f2; cursor:pointer;">
<div class="text-center px-3">
<!-- ЛОГОТИП -->
<img src="/assets/img/logo.png" alt="Логотип" style="height:110px;" class="mb-4">
<!-- НАЗВАНИЕ УЧРЕЖДЕНИЯ -->
<div style="font-size:32px; font-weight:700; line-height:1.2; color:#000;">
Психоневрологический<br>
диспансер №8
</div>
<!-- Заголовок страницы -->
<h1 class="mt-4" style="font-size:42px; font-weight:600; color:#000;">
Расписание приема врачей<br>и участков
</h1>
<div class="mt-3" style="font-size:20px; color:#333;">
(поиск по адресу проживания)
</div>
</div>
</div>
</a>
<script>
// Скрываем прелоадер после загрузки страницы
window.addEventListener('load', function() {
document.getElementById('loader').style.opacity = '0';
setTimeout(() => {
document.getElementById('loader').style.display = 'none';
}, 400);
});
</script>

View File

@ -0,0 +1,20 @@
<?php
class site_screensaver extends module {
public $templBlade = 'site_screensaver';
function _on_() {
global $blade;
return $blade->run($this->getBladeTempl('main'));
}
}
?>

View File

@ -33,89 +33,47 @@
<div class="col pl-4 ml-4 right-col position-relative pr-0"> <div class="col pl-4 ml-4 right-col position-relative pr-0">
<div id="bigcontent-wrapper" class="h-100 position-relative"> <div id="bigcontent-wrapper" class="h-100 position-relative">
<div id="bigcontent" class="h-100" style="overflow: hidden;"> <div class="h-100" style="overflow: auto;">
{body} {body}
</div> </div>
<!-- Кнопки прокрутки -->
<button id="scroll-up" class="scroll-btn">&#9650;</button>
<button id="scroll-down" class="scroll-btn">&#9660;</button>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
$(function () { (function () {
var $box = $('#bigcontent'); var REDIRECT_TIMEOUT = 3 * 60 * 1000; // 3 минуты
var $btnUp = $('#scroll-up'); var timer = null;
var $btnDn = $('#scroll-down');
var interval = null; function resetTimer() {
var step = 250; // шаг скролла clearTimeout(timer);
var delay = 60; // частота скролла (мс) timer = setTimeout(function () {
window.location.href = "/";
function checkOverflow() { }, REDIRECT_TIMEOUT);
var el = $box[0];
if (!el) return;
// немного страховки на округления браузера
if (el.scrollHeight - 2 > el.clientHeight) {
$btnUp.show();
$btnDn.show();
} else {
$btnUp.hide();
$btnDn.hide();
}
} }
function startScroll(dir) { // Сброс на любое действие пользователя
stopScroll(); var events = ['click', 'mousemove', 'keydown', 'scroll', 'touchstart'];
interval = setInterval(function () {
$box[0].scrollTop += dir * step; events.forEach(function (ev) {
}, delay); window.addEventListener(ev, resetTimer, true);
});
// Сброс на прокрутку контентной области
document.addEventListener("DOMContentLoaded", function() {
var content = document.querySelector('#bigcontent-wrapper');
if (content) {
content.addEventListener('scroll', resetTimer, true);
} }
function stopScroll() {
if (interval) {
clearInterval(interval);
interval = null;
}
}
// Наверх
$btnUp.on('mousedown touchstart', function (e) {
e.preventDefault();
startScroll(-1);
}); });
// Вниз // Запуск при открытии страницы
$btnDn.on('mousedown touchstart', function (e) { resetTimer();
e.preventDefault(); })();
startScroll(1);
});
// Остановка
$(document).on('mouseup mouseleave touchend touchcancel', function () {
stopScroll();
});
// 1) Проверка при старте
checkOverflow();
// 2) Периодический опрос — чтобы ловить любые изменения в runtime
setInterval(checkOverflow, 500);
// 3) Если используешь Bootstrap collapse / модалки / табы — дергаем ещё раз
$(document).on('shown.bs.collapse hidden.bs.collapse shown.bs.tab hidden.bs.tab', function () {
checkOverflow();
});
});
</script> </script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<title>{auto_title}</title>
<meta name="keywords" content="{meta_keywords}">
<meta name="description" content="{meta_description}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<base href="{_SITE_ROOT_}">
<link rel="icon" href="favicon.ico">
{include:include/_static_css_headers.htm}
{include:include/_static_js_headers.htm}
{add_headers}
</head>
<body class="w-100 h-100">
{body}
</body>
</html>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf8"?>
<templ filename="index_empty.htm">
</templ>

View File

@ -18,36 +18,6 @@ services:
networks: networks:
- app_network - app_network
php-cron:
build:
context: .
dockerfile: docker/dockerfile_cron
image: angels-it/php-cron
environment:
APP_ENV: ${APP_ENV}
DB_HOST: mariadb
DB_DATABASE: ${MYSQL_DATABASE}
DB_USERNAME: ${MYSQL_USER}
DB_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- ./app:/var/www/html
- ./config/php:/usr/local/etc/php
- ./config/cron:/etc/cron.d
- ./logs/php-cron:/var/log
command: >
/bin/sh -c '
if [ "$${APP_ENV}" = "prod" ]; then
echo "[cron] production mode — starting cron";
cron -f;
else
echo "[cron] dev mode — sleeping";
tail -f /dev/null;
fi
'
restart: always
networks:
- app_network
nginx: nginx:
image: nginx:1.24.0 image: nginx:1.24.0
ports: ports:

1
sync_data.cmd Normal file
View File

@ -0,0 +1 @@
docker exec -t pnd8_app-php-1 sh -c "/var/www/html/engine/cron/data_sync/run.sh"