Compare commits

..

17 Commits

Author SHA1 Message Date
BartekGit 142c40b00b Invoice: Kreator powiadomień cyklicznych 2026-07-30 00:26:26 +02:00
BartekGit 8b92aac1db Ustawienia powiadomień faktur cyklicznych 2026-07-30 00:23:02 +02:00
BartekGit e6ba6152a6 Invoice: Dopracowanie ekranu tworzenia faktur cyklicznych 2026-07-30 00:08:09 +02:00
BartekGit 7905529880 Invoice: Moduł faktur cyklicznych 2026-07-29 23:56:32 +02:00
BartekGit de562d164c Merge branch 'main' of ssh://gitrepos.serwer.magico.pl:2221/BartekGit/magico.prototype 2026-07-21 19:12:52 +02:00
BartekGit 2c1f1b2a20 Telemetria: Widok mapy instalacji 2026-07-21 18:52:33 +02:00
oskar n af946d338a Merge branch 'rcp-mobile' 2026-06-24 09:39:52 +02:00
BartekGit 2d749776fc Telemetria: Dashboard v2 2026-06-19 15:35:45 +02:00
BartekGit 351898cdc5 Telemetria: Dodanie dashboardów 2026-06-19 15:18:28 +02:00
BartekGit b3a24f821b Telemetria: Edycja dashboardu i intalacji 2026-06-18 10:33:50 +02:00
BartekGit 9648214675 feat: add consumption report page with installation filters and data visualization 2026-06-18 09:11:08 +02:00
BartekGit 8ed8d3bb42 Telemetria: Raporty 2026-06-18 08:41:49 +02:00
BartekGit a9327760d0 Telemetria: INIT i wstępna makieta 2026-06-18 07:20:14 +02:00
user caaf914865 fix: poprawki date picker w grafik feat: alert icon w topbar 2026-06-16 15:23:08 +02:00
user 83d3499164 feat: date selector w Grafik pracy, Tab historii oraz statystyki w Urlopy 2026-06-16 14:41:37 +02:00
BartekGit 83c055cc09 Aplikacja Magico Pracownik 2026-05-28 12:56:03 +02:00
BartekGit 9b3c3848a7 Inovice / Travel: Dodanie do makiety obsługi rodzajó faktur 2026-05-13 08:41:12 +02:00
30 changed files with 6937 additions and 27 deletions
+80
View File
@@ -0,0 +1,80 @@
<?php
$basePath = '';
if ($_SERVER['HTTP_HOST'] === 'localhost' || $_SERVER['HTTP_HOST'] === '127.0.0.1') {
$basePath = '/magico-prototype';
}
?>
<!DOCTYPE html>
<html lang="en" class="light-style layout-navbar-fixed layout-menu-fixed layout-compact" dir="ltr"
data-theme="theme-default" data-assets-path="<?= $basePath ?>/assets/"
data-template="vertical-menu-template-no-customizer-starter">
<head>
<meta charset="utf-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
<title>telemetria.magico</title>
<meta name="description" content="" />
<!-- Favicon -->
<link rel="icon" type="image/x-icon" href="<?= $basePath ?>/assets/img/favicon/favicon.ico" />
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Public+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap"
rel="stylesheet" />
<link rel="stylesheet" href="<?= $basePath ?>/assets/vendor/fonts/boxicons.css" />
<!-- <link rel="stylesheet" href="<?= $basePath ?>/assets/vendor/fonts/fontawesome.css" /> -->
<!-- <link rel="stylesheet" href="<?= $basePath ?>/assets/vendor/fonts/flag-icons.css" /> -->
<!-- Core CSS -->
<link rel="stylesheet" href="<?= $basePath ?>/assets/vendor/css/rtl/core.css" />
<link rel="stylesheet" href="<?= $basePath ?>/assets/vendor/css/rtl/theme-default.css" />
<link rel="stylesheet" href="<?= $basePath ?>/assets/css/demo.css" />
<!-- Vendors CSS -->
<link rel="stylesheet" href="<?= $basePath ?>/assets/vendor/libs/perfect-scrollbar/perfect-scrollbar.css" />
<!-- Page CSS -->
<!-- Helpers -->
<script src="<?= $basePath ?>/assets/vendor/js/helpers.js"></script>
<!--! Template customizer & Theme config files MUST be included after core stylesheets and helpers.js in the <head> section -->
<!--? Config: Mandatory theme config file contain global vars & default theme options, Set your preferred theme option in this file. -->
<script src="<?= $basePath ?>/assets/js/config.js"></script>
<?php if (!empty($enablePrototypeComments) && $enablePrototypeComments): ?>
<link rel="stylesheet" href="<?= $basePath ?>/assets/css/comments.css">
<?php endif; ?>
<style>
@media (min-width: 1200px) {
.layout-menu-fixed .layout-menu,
.layout-menu-fixed-offcanvas .layout-menu {
padding-top: 50px;
}
}
</style>
</head>
<body>
<!-- Layout wrapper -->
<div class="layout-wrapper layout-content-navbar">
<div class="layout-container">
<!-- Menu -->
<?php include 'menu.php'; ?>
<!-- / Menu -->
<!-- Layout container -->
<div class="layout-page pt-0" style="padding-top: 0 !important;">
<!-- Content wrapper -->
<div class="content-wrapper">
@@ -0,0 +1,496 @@
<?php
$enablePrototypeComments = true;
include '../../header-invoice.php';
?>
<div class="container-fluid flex-grow-1 container-p-y">
<!-- Header & Form Buttons -->
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-4 gap-3">
<div class="d-flex flex-column justify-content-center">
<h4 class="mb-1 text-body"><i class="bx bx-repeat me-2 text-primary"></i>Nowy Szablon Faktury Cyklicznej</h4>
<p class="text-muted mb-0">Skonfiguruj reguły automatycznego generowania i wystawiania dokumentów abonamentowych</p>
</div>
<div class="d-flex align-content-center flex-wrap gap-2">
<a href="app-invoice-recurring-list.php" class="btn btn-label-secondary">Anuluj</a>
<button type="submit" form="recurringForm" class="btn btn-primary"><i class="bx bx-save me-1"></i> Zapisz szablon cyklu</button>
</div>
</div>
<form id="recurringForm" onsubmit="return false">
<!-- Top Section: Status & Cycle Rules -->
<div class="row mb-4">
<!-- Card 1: Harmonogram i Reguły Cyklu -->
<div class="col-lg-6 mb-4 mb-lg-0">
<div class="card mb-4">
<div class="card-header border-bottom py-3 d-flex justify-content-between align-items-center bg-label-primary">
<h6 class="card-title m-0 text-primary"><i class="bx bx-calendar-repeat me-2"></i>Harmonogram i Reguły Cyklu</h6>
<!-- Global Master Switch ON/OFF -->
<div class="d-flex align-items-center gap-2 me-4">
<span class="fw-bold small text-body">Status cyklu:</span>
<div class="form-check form-switch m-0 d-flex align-items-center">
<input class="form-check-input mt-0 me-3" type="checkbox" id="masterStatusToggle" checked style="width: 2.5em; height: 1.3em; cursor: pointer;">
<label class="form-check-label mb-0 ms-1" for="masterStatusToggle" style="cursor: pointer;">
<span class="badge bg-label-success" id="statusLabelText">AKTYWNY (ON)</span>
</label>
</div>
</div>
</div>
<div class="card-body pt-3">
<div class="mb-3">
<label class="form-label fw-bold">Nazwa własna cyklu / Szablonu</label>
<input type="text" class="form-control" placeholder="np. Abonament IT - Usługi Chmurowe (ABC Logistics)" value="Abonament IT i Wsparcie Techniczne">
<small class="text-muted">Nazwa widoczna w rejestrze cykli dla ułatwienia identyfikacji.</small>
</div>
<!-- Przebudowany Konfigurator Cyklu -->
<div class="card shadow-none border bg-lighter mb-3">
<div class="card-body p-3">
<h6 class="fw-bold mb-3 text-primary"><i class="bx bx-time-five me-1"></i>Konfiguracja Powtarzania</h6>
<!-- Data rozpoczęcia & Cykl rozliczeń -->
<div class="row g-2 mb-3">
<div class="col-md-6">
<label class="form-label fw-bold">Data rozpoczęcia</label>
<input type="text" class="form-control date-picker" id="startDateInput" value="2026-08-01">
<small class="text-muted">Dzień wystawienia pierwszej faktury.</small>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Cykl rozliczeń</label>
<select class="form-select" id="cycleUnitSelect">
<option value="miesiac" selected>Miesiąc</option>
<option value="rok">Rok</option>
<option value="dzien">Dzień</option>
</select>
<small class="text-muted">Jednostka czasu dla powtarzania.</small>
</div>
</div>
<!-- Interwał & Obowiązuje do -->
<div class="row g-2 mb-3">
<div class="col-md-6">
<label class="form-label fw-bold">Interwał (Co ile cykli)</label>
<div class="input-group">
<span class="input-group-text">Co</span>
<input type="number" class="form-control" value="1" min="1" id="intervalInput">
<span class="input-group-text" id="intervalUnitText">miesiąc</span>
</div>
<small class="text-muted">np. "1" = co miesiąc, "3" = kwartalnie, "12" = co rok.</small>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">Obowiązuje do</label>
<input type="text" class="form-control date-picker" id="endDateInput" placeholder="RRRR-MM-DD (Bezterminowo)">
<small class="text-muted">Data, po której system automatycznie wyłączy cykl.</small>
</div>
</div>
<!-- Informacyjna ramka dynamicznego opisu -->
<div class="alert alert-primary mb-0 p-2 d-flex align-items-center" role="alert">
<i class="bx bx-info-circle fs-4 me-2"></i>
<span class="small" id="summaryDescription">
Faktura będzie generowana <strong>co 1 miesiąc</strong>, rozpoczynając od dnia <strong>01.08.2026 r.</strong> bezterminowo.
</span>
</div>
</div>
</div>
<hr class="my-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="emailAutoSend" checked>
<label class="form-check-label fw-bold" for="emailAutoSend">Automatyczna wysyłka e-mail do kontrahenta</label>
<div class="text-muted small">Wyślij e-mail z podpiętą fakturą w PDF zaraz po automatycznym wystawieniu (zgodnie z <b>globalnym szablonem e-mail</b> zdefiniowanym w Ustawieniach Systemowych).</div>
</div>
</div>
</div>
</div>
<!-- Card 2: Kontrahent - Nabywca i Odbiorca -->
<div class="col-lg-6">
<div class="card mb-4">
<div class="card-header border-bottom py-3 d-flex justify-content-between align-items-center">
<h6 class="card-title m-0"><i class="bx bx-user-check me-2 text-primary"></i>Kontrahent (Nabywca i Odbiorca)</h6>
</div>
<div class="card-body pt-3">
<!-- Nabywca -->
<div class="mb-3">
<label class="form-label fw-bold">Nabywca (Główny kontrahent / Płatnik)</label>
<div class="card shadow-none border bg-lighter">
<div class="card-body p-3 d-flex justify-content-between align-items-center">
<div>
<h6 class="mb-1 text-primary fw-bold">ABC Logistics Sp. z o.o.</h6>
<p class="mb-0 small text-muted">NIP: PL 865 223 11 90 • ul. Warszawska 45, 00-001 Warszawa</p>
<p class="mb-0 small text-muted">E-mail do faktur: ksiegowosc@abclogistics.pl</p>
</div>
<button type="button" class="btn btn-outline-primary btn-sm"><i class="bx bx-refresh me-1"></i> Zmień</button>
</div>
</div>
</div>
<!-- Switches for Recipient and Extra -->
<div class="d-flex flex-column gap-2 mb-3">
<label class="form-check form-switch m-0">
<input class="form-check-input" type="checkbox" id="recipientToggle">
<span class="form-check-label fw-bold">Wskaż innego Odbiorcę / Miejsce dostawy</span>
</label>
</div>
<!-- Odbiorca Container (Domyślnie ukryty) -->
<div class="card shadow-none border mb-3 bg-white d-none" id="recipientBox">
<div class="card-header py-2 bg-label-secondary d-flex justify-content-between align-items-center">
<span class="small fw-bold"><i class="bx bx-buildings me-1"></i>Dane Odbiorcy (np. Szkoła, Oddział, Filia)</span>
<button type="button" class="btn btn-xs btn-outline-secondary">Wybierz z listy</button>
</div>
<div class="card-body p-3">
<div class="mb-2">
<label class="form-label small text-muted mb-1">Nazwa Odbiorcy</label>
<input type="text" class="form-control form-control-sm" value="ABC Logistics - Oddział Magazynowy Stalowa Wola">
</div>
<div class="row g-2">
<div class="col-8">
<label class="form-label small text-muted mb-1">Adres Odbiorcy</label>
<input type="text" class="form-control form-control-sm" value="ul. Przemysłowa 12, 37-450 Stalowa Wola">
</div>
<div class="col-4">
<label class="form-label small text-muted mb-1">NIP Odbiorcy (opcja)</label>
<input type="text" class="form-control form-control-sm" placeholder="Opcjonalny NIP">
</div>
</div>
</div>
</div>
<div class="row g-2">
<div class="col-6">
<label class="form-label small text-uppercase fw-bold text-muted">Miejsce wystawienia</label>
<input type="text" class="form-control" value="Stalowa Wola">
</div>
<div class="col-6">
<label class="form-label small text-uppercase fw-bold text-muted">Osoba wystawiająca</label>
<input type="text" class="form-control" value="Bartłomiej Banaczyk">
</div>
</div>
</div>
</div>
<!-- Kontakt i powiadomienia -->
<div class="card mb-4">
<div class="card-body">
<h6 class="card-title text-primary mb-3">Kontakt i powiadomienia</h6>
<div class="mb-3">
<label class="form-label small text-uppercase text-muted fw-bold">Email kontaktowy</label>
<input type="email" class="form-control" value="ksiegowosc@abclogistics.pl">
</div>
<div class="mb-3">
<label class="form-label small text-uppercase text-muted fw-bold">Wysyłka faktur e-mail</label>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="autoEmail" checked>
<label class="form-check-label" for="autoEmail">Automatyczna wysyłka wg profilu:</label>
</div>
<select class="form-select form-select-sm">
<option value="1">Profil: Standardowy (Termin 30 dni)</option>
<option value="2">Profil: Szybki (Termin 3 dni)</option>
<option value="3">Profil: Tylko przypomnienia po terminie</option>
</select>
</div>
<div class="mb-3">
<label class="form-label small text-uppercase text-muted fw-bold">Numer SMS</label>
<input type="text" class="form-control" placeholder="+48 ...">
</div>
<div class="mb-0">
<label class="form-label small text-uppercase text-muted fw-bold">Powiadomienia SMS</label>
<select class="form-select">
<option value="" selected>Wybierz opcję</option>
<option value="1">Wyślij powiadomienie SMS po wygenerowaniu</option>
<option value="0">Brak powiadomień</option>
</select>
</div>
</div>
</div>
<!-- Ostatni dokument -->
<div class="card mb-4 mb-lg-0">
<div class="card-body">
<h6 class="card-title text-primary mb-3">Ostatni dokument</h6>
<p class="text-muted mb-0 small">Brak dokumentów</p>
</div>
</div>
</div>
</div>
<!-- Section 2: Document Settings & Variables -->
<div class="card mb-4">
<div class="card-header border-bottom py-3">
<h6 class="card-title m-0"><i class="bx bx-slider-alt me-2 text-primary"></i>Ustawienia Dokumentów Generowanych</h6>
</div>
<div class="card-body pt-3">
<div class="row g-3">
<div class="col-md-3">
<label class="form-label fw-bold">Rodzaj dokumentu</label>
<select class="form-select border-primary text-primary fw-bold">
<option value="faktura" selected>Faktura VAT</option>
<option value="proforma">Proforma</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label fw-bold">Waluta & Język</label>
<div class="input-group">
<select class="form-select">
<option value="PLN" selected>PLN (zł)</option>
<option value="EUR">EUR (€)</option>
<option value="USD">USD ($)</option>
</select>
<select class="form-select">
<option value="PL" selected>PL</option>
<option value="EN">EN</option>
</select>
</div>
</div>
<div class="col-md-3">
<label class="form-label fw-bold">Forma płatności</label>
<select class="form-select">
<option value="przelew" selected>Przelew bankowy</option>
<option value="gotowka">Gotówka</option>
<option value="split">Split Payment (MPP)</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label fw-bold">Termin płatności</label>
<div class="input-group">
<input type="number" class="form-control" value="14">
<span class="input-group-text">dni</span>
</div>
</div>
</div>
<div class="row g-3 mt-1">
<div class="col-md-3">
<label class="form-label fw-bold">Konto bankowe</label>
<select class="form-select">
<option value="1">Main PLN: 89 1050 ... 4491</option>
<option value="2">EUR IBAN: PL 12 1050 ... 9901</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-bold">Data sprzedaży</label>
<select class="form-select">
<option value="issue_date" selected>Zgodna z datą wystawienia</option>
<option value="last_day_prev_month">Ostatni dzień poprzedniego miesiąca</option>
<option value="first_day_curr_month">Pierwszy dzień bieżącego miesiąca</option>
</select>
</div>
<div class="col-md-5">
<label class="form-label fw-bold">Komentarz / Uwagi na fakturze (dla klienta)</label>
<input type="text" class="form-control" value="Opłata abonamentowa za miesiąc {MIESIAC} {ROK}. Dziękujemy za współpracę!">
<div class="mt-1">
<span class="badge bg-label-secondary me-1" style="cursor:pointer;">+ {MIESIAC}</span>
<span class="badge bg-label-secondary me-1" style="cursor:pointer;">+ {ROK}</span>
<span class="badge bg-label-secondary me-1" style="cursor:pointer;">+ {POPRZEDNI_MIESIAC}</span>
<span class="badge bg-label-secondary" style="cursor:pointer;">+ {KWARTAL}</span>
</div>
</div>
</div>
</div>
</div>
<!-- Section 3: Items Configurator with Dynamic Tags -->
<div class="card mb-4">
<div class="card-header bg-label-primary py-2 d-flex justify-content-between align-items-center">
<span class="fw-bold text-primary"><i class="bx bx-list-plus me-2"></i>Pozycje na fakturze cyklicznej</span>
<div class="d-flex align-items-center gap-3">
<div class="d-flex align-items-center">
<label class="text-muted small me-2 text-nowrap">Licz od:</label>
<select class="form-select form-select-sm">
<option value="netto" selected>Cen netto</option>
<option value="brutto">Cen brutto</option>
</select>
</div>
<button type="button" class="btn btn-primary btn-sm"><i class="bx bx-plus me-1"></i> Dodaj pozycję</button>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-bordered m-0">
<thead class="table-light">
<tr>
<th style="width: 35%;">Nazwa towaru / usługi (oraz zmienne okresu)</th>
<th style="width: 10%;">Ilość</th>
<th style="width: 10%;">J.m.</th>
<th style="width: 12%;">Cena netto</th>
<th style="width: 10%;">VAT</th>
<th style="width: 13%;">Wartość brutto</th>
<th style="width: 5%;" class="text-center">Usuń</th>
</tr>
</thead>
<tbody>
<!-- Item row 1 -->
<tr>
<td>
<input type="text" class="form-control mb-1 fw-medium" value="Abonament za opiekę serwerową IT - okres {MIESIAC} {ROK}">
</td>
<td><input type="number" class="form-control" value="1"></td>
<td>
<select class="form-select">
<option value="usluga" selected>usł.</option>
<option value="szt">szt.</option>
<option value="msc">m-c</option>
</select>
</td>
<td><input type="text" class="form-control" value="1 200,00"></td>
<td>
<select class="form-select">
<option value="23" selected>23%</option>
<option value="8">8%</option>
<option value="0">0%</option>
<option value="zw">zw.</option>
</select>
</td>
<td><input type="text" class="form-control fw-bold" value="1 476,00" readonly></td>
<td class="text-center align-middle">
<button type="button" class="btn btn-icon btn-sm btn-label-danger"><i class="bx bx-trash"></i></button>
</td>
</tr>
<!-- Item row 2 -->
<tr>
<td>
<input type="text" class="form-control mb-1 fw-medium" value="Hosting Chmurowy Magico Cloud (Kwartalnie)">
</td>
<td><input type="number" class="form-control" value="1"></td>
<td>
<select class="form-select">
<option value="usluga" selected>usł.</option>
<option value="szt">szt.</option>
</select>
</td>
<td><input type="text" class="form-control" value="300,00"></td>
<td>
<select class="form-select">
<option value="23" selected>23%</option>
</select>
</td>
<td><input type="text" class="form-control fw-bold" value="369,00" readonly></td>
<td class="text-center align-middle">
<button type="button" class="btn btn-icon btn-sm btn-label-danger"><i class="bx bx-trash"></i></button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Dynamic Tag Guide below table -->
<div class="p-3 border-top bg-lighter">
<div class="d-flex align-items-center gap-2">
<small class="text-muted fw-bold">Dostępne zmienne dla pozycji (kliknij aby skopiować):</small>
<span class="badge bg-label-primary px-2 py-1" style="cursor:pointer;">{MIESIAC}</span>
<span class="badge bg-label-primary px-2 py-1" style="cursor:pointer;">{ROK}</span>
<span class="badge bg-label-primary px-2 py-1" style="cursor:pointer;">{POPRZEDNI_MIESIAC}</span>
<span class="badge bg-label-primary px-2 py-1" style="cursor:pointer;">{KWARTAL}</span>
</div>
</div>
</div>
<div class="card-footer bg-lighter d-flex justify-content-end py-3 border-top">
<div class="d-flex flex-column align-items-end me-3">
<span class="text-muted small">Suma netto: <strong>1 500,00 PLN</strong></span>
<span class="text-muted small">Suma VAT (23%): <strong>345,00 PLN</strong></span>
<h5 class="mb-0 text-primary mt-1">Razem brutto: <strong>1 845,00 PLN</strong></h5>
</div>
</div>
</div>
<!-- Submit Bottom Bar -->
<div class="d-flex justify-content-end gap-2 mb-4">
<a href="app-invoice-recurring-list.php" class="btn btn-label-secondary">Anuluj</a>
<button type="submit" class="btn btn-primary"><i class="bx bx-save me-1"></i> Zapisz szablon cyklu</button>
</div>
</form>
</div>
<?php include '../../footer.php'; ?>
<script>
document.addEventListener('DOMContentLoaded', function () {
const toggle = document.getElementById('masterStatusToggle');
const label = document.getElementById('statusLabelText');
const unitSelect = document.getElementById('cycleUnitSelect');
const intervalText = document.getElementById('intervalUnitText');
if (toggle && label) {
toggle.addEventListener('change', function () {
if (this.checked) {
label.textContent = 'AKTYWNY (ON)';
label.className = 'badge bg-label-success';
} else {
label.textContent = 'WYŁĄCZONY (OFF)';
label.className = 'badge bg-label-danger';
}
});
}
const intervalInput = document.getElementById('intervalInput');
const summaryDesc = document.getElementById('summaryDescription');
const startDateInput = document.getElementById('startDateInput');
const endDateInput = document.getElementById('endDateInput');
function updateUnitsAndSummary() {
if (!unitSelect || !intervalInput || !intervalText) return;
const val = unitSelect.value;
const num = parseInt(intervalInput.value) || 1;
let unitLabel = '';
let unitGenitive = '';
if (val === 'miesiac') {
if (num === 1) unitLabel = 'miesiąc';
else if (num >= 2 && num <= 4) unitLabel = 'miesiące';
else unitLabel = 'miesięcy';
unitGenitive = unitLabel;
} else if (val === 'rok') {
if (num === 1) unitLabel = 'rok';
else if (num >= 2 && num <= 4) unitLabel = 'lata';
else unitLabel = 'lat';
unitGenitive = unitLabel;
} else if (val === 'dzien') {
if (num === 1) unitLabel = 'dzień';
else unitLabel = 'dni';
unitGenitive = unitLabel;
}
intervalText.textContent = unitLabel;
if (summaryDesc) {
const startDate = startDateInput ? startDateInput.value || 'dzisiaj' : 'dzisiaj';
const endDate = endDateInput && endDateInput.value ? ` do dnia <strong>${endDateInput.value}</strong>` : ' bezterminowo';
summaryDesc.innerHTML = `Faktura będzie generowana <strong>co ${num} ${unitGenitive}</strong>, rozpoczynając od dnia <strong>${startDate}</strong>${endDate}.`;
}
}
if (unitSelect) unitSelect.addEventListener('change', updateUnitsAndSummary);
if (intervalInput) intervalInput.addEventListener('input', updateUnitsAndSummary);
if (startDateInput) startDateInput.addEventListener('change', updateUnitsAndSummary);
if (endDateInput) endDateInput.addEventListener('change', updateUnitsAndSummary);
const recipientToggle = document.getElementById('recipientToggle');
const recipientBox = document.getElementById('recipientBox');
if (recipientToggle && recipientBox) {
recipientToggle.addEventListener('change', function () {
if (this.checked) {
recipientBox.classList.remove('d-none');
} else {
recipientBox.classList.add('d-none');
}
});
}
});
</script>
</body>
</html>
@@ -0,0 +1,510 @@
<?php
$enablePrototypeComments = true;
include '../../header-invoice.php';
?>
<div class="container-fluid flex-grow-1 container-p-y">
<h4 class="py-3 mb-4">
<span class="text-muted fw-light">Sprzedaż /</span> Faktury cykliczne
</h4>
<!-- Summary KPI Cards -->
<div class="row g-4 mb-4">
<div class="col-sm-6 col-xl-3">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-start justify-content-between">
<div class="content-left">
<span class="text-heading">Aktywne cykle</span>
<div class="d-flex align-items-center my-1">
<h4 class="mb-0 me-2">18</h4>
<span class="text-success small fw-medium">nowe w tym roku</span>
</div>
<small class="text-muted">Szablonów zlecających wysyłkę</small>
</div>
<div class="avatar">
<span class="avatar-initial rounded bg-label-primary">
<i class="bx bx-repeat bx-sm"></i>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-start justify-content-between">
<div class="content-left">
<span class="text-heading">Szacowany MRR</span>
<div class="d-flex align-items-center my-1">
<h4 class="mb-0 me-2">42 850,00 zł</h4>
</div>
<small class="text-muted">Przychód powtarzalny / m-c</small>
</div>
<div class="avatar">
<span class="avatar-initial rounded bg-label-success">
<i class="bx bx-trending-up bx-sm"></i>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-start justify-content-between">
<div class="content-left">
<span class="text-heading">Szacowany ARR</span>
<div class="d-flex align-items-center my-1">
<h4 class="mb-0 me-2">514 200,00 zł</h4>
</div>
<small class="text-muted">Przychód powtarzalny / rok</small>
</div>
<div class="avatar">
<span class="avatar-initial rounded bg-label-info">
<i class="bx bx-calendar-event bx-sm"></i>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-start justify-content-between">
<div class="content-left">
<span class="text-heading">Wyłączone (Nieaktywne)</span>
<div class="d-flex align-items-center my-1">
<h4 class="mb-0 me-2">3</h4>
</div>
<small class="text-muted">Cykle obecnie wyłączone</small>
</div>
<div class="avatar">
<span class="avatar-initial rounded bg-label-warning">
<i class="bx bx-pause-circle bx-sm"></i>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Main Grid: Table Left (9 cols), Filter Panel Right (3 cols) -->
<div class="row">
<!-- Table Column -->
<div class="col-lg-9" id="invoice-list-col">
<div class="card">
<div
class="card-header border-bottom d-flex justify-content-between align-items-center flex-wrap gap-2">
<div class="me-2">
<div class="input-group input-group-merge">
<span class="input-group-text" id="basic-addon-search31"><i class="bx bx-search"></i></span>
<input type="text" class="form-control" placeholder="Wpisz aby wyszukać cykl..."
aria-label="Search..." aria-describedby="basic-addon-search31">
</div>
</div>
<div class="d-flex align-items-center gap-2">
<div class="dropdown">
<button type="button" class="btn btn-label-secondary p-2" data-bs-toggle="dropdown"
aria-expanded="false">
<i class="bx bx-cog"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<h6 class="dropdown-header">Widoczność kolumn</h6>
</li>
<li>
<div class="dropdown-item">
<div class="form-check"><input class="form-check-input" type="checkbox" checked>
<label class="form-check-label">Nazwa cyklu</label>
</div>
</div>
</li>
<li>
<div class="dropdown-item">
<div class="form-check"><input class="form-check-input" type="checkbox" checked>
<label class="form-check-label">Kontrahent</label>
</div>
</div>
</li>
<li>
<div class="dropdown-item">
<div class="form-check"><input class="form-check-input" type="checkbox" checked>
<label class="form-check-label">Wartość brutto</label>
</div>
</div>
</li>
</ul>
</div>
<button type="button" class="btn btn-label-secondary d-flex align-items-center gap-2"
id="filter-toggle-btn">
<i class="bx bx-filter-alt"></i> Filtry
</button>
<select class="form-select" style="width: 80px;">
<option value="40">40</option>
<option value="60">60</option>
<option value="100">100</option>
</select>
<a href="app-invoice-recurring-add.php" class="btn btn-primary">
<i class="bx bx-plus me-1"></i> <span class="d-none d-sm-inline-block">Utwórz fakturę
cykliczną</span>
</a>
</div>
</div>
<div class="table-responsive text-nowrap" style="min-height: 400px;">
<table class="table table-hover table-striped">
<thead class="table-light">
<tr>
<th style="width: 50px;">#</th>
<th>Nazwa cyklu i rodzaj</th>
<th>Nabywca / Odbiorca</th>
<th>Wartość netto</th>
<th>Wartość brutto</th>
<th>Harmonogram</th>
<th>Najbliższa emisja</th>
<th>Status</th>
<th class="text-center">Akcje</th>
</tr>
</thead>
<tbody class="table-border-bottom-0">
<!-- Item 1 -->
<tr>
<td>1</td>
<td>
<div class="d-flex flex-column">
<a href="app-invoice-recurring-add.php" class="fw-bold text-body">Abonament
Serwis IT i Hosting</a>
<small class="text-muted">Faktura VAT</small>
</div>
</td>
<td>
<div class="d-flex flex-column">
<span class="fw-semibold">ABC Logistics Sp. z o.o.</span>
<small class="text-muted">NIP: 865-223-11-90</small>
</div>
</td>
<td>
<span class="text-heading">1 500,00 PLN</span>
</td>
<td>
<span class="fw-bold text-heading">1 845,00 PLN</span>
</td>
<td>
<span class="badge bg-label-info"><i class="bx bx-calendar me-1"></i> Co 1 miesiąc</span>
<br><small class="text-muted">Bezterminowo</small>
</td>
<td>
<span class="fw-medium text-primary">01.08.2026</span>
<br><small class="text-muted">Ostatnio: 01.07.2026</small>
</td>
<td>
<div class="form-check form-switch m-0" data-bs-toggle="tooltip" title="Włącz / Wyłącz cykl">
<input class="form-check-input" type="checkbox" checked style="cursor: pointer;">
<span class="badge bg-label-success ms-1"><i class="bx bx-power-off me-1"></i> ON</span>
</div>
</td>
<td class="text-center">
<div class="d-flex justify-content-center align-items-center gap-1">
<a href="app-invoice-recurring-add.php"
class="btn btn-icon btn-sm text-secondary" data-bs-toggle="tooltip"
title="Edytuj szablon">
<i class="bx bx-edit-alt fs-5"></i>
</a>
<div class="dropdown">
<button type="button" class="btn p-0 dropdown-toggle hide-arrow"
data-bs-toggle="dropdown">
<i class="bx bx-dots-vertical-rounded fs-5"></i>
</button>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="javascript:void(0);"><i
class="bx bx-power-off me-1"></i> Wyłącz cykl</a>
<a class="dropdown-item" href="app-invoice-list.php"><i
class="bx bx-history me-1"></i> Historia wygenerowanych
faktur</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger" href="javascript:void(0);"><i
class="bx bx-archive-in me-1"></i> Archiwizuj cykl</a>
</div>
</div>
</div>
</td>
</tr>
<!-- Item 2 -->
<tr>
<td>2</td>
<td>
<div class="d-flex flex-column">
<a href="app-invoice-recurring-add.php" class="fw-bold text-body">Obsługa
Księgowa i Kadrowa</a>
<small class="text-muted">Faktura VAT</small>
</div>
</td>
<td>
<div class="d-flex flex-column">
<span class="fw-semibold">Bud-Spec Budownictwo S.A.</span>
<small class="text-muted">NIP: 111-222-33-44 • Odbiorca: Oddział Warszawa</small>
</div>
</td>
<td>
<span class="text-heading">3 500,00 PLN</span>
</td>
<td>
<span class="fw-bold text-heading">4 305,00 PLN</span>
</td>
<td>
<span class="badge bg-label-info"><i class="bx bx-calendar me-1"></i> Co 1 miesiąc</span>
<br><small class="text-muted">Do: 31.12.2026</small>
</td>
<td>
<span class="fw-medium text-primary">10.08.2026</span>
<br><small class="text-muted">Ostatnio: 10.07.2026</small>
</td>
<td>
<div class="form-check form-switch m-0" data-bs-toggle="tooltip" title="Włącz / Wyłącz cykl">
<input class="form-check-input" type="checkbox" checked style="cursor: pointer;">
<span class="badge bg-label-success ms-1"><i class="bx bx-power-off me-1"></i> ON</span>
</div>
</td>
<td class="text-center">
<div class="d-flex justify-content-center align-items-center gap-1">
<a href="app-invoice-recurring-add.php"
class="btn btn-icon btn-sm text-secondary" data-bs-toggle="tooltip"
title="Edytuj szablon">
<i class="bx bx-edit-alt fs-5"></i>
</a>
<div class="dropdown">
<button type="button" class="btn p-0 dropdown-toggle hide-arrow"
data-bs-toggle="dropdown">
<i class="bx bx-dots-vertical-rounded fs-5"></i>
</button>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="javascript:void(0);"><i
class="bx bx-power-off me-1"></i> Wyłącz cykl</a>
<a class="dropdown-item" href="app-invoice-list.php"><i
class="bx bx-history me-1"></i> Historia wygenerowanych
faktur</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger" href="javascript:void(0);"><i
class="bx bx-archive-in me-1"></i> Archiwizuj cykl</a>
</div>
</div>
</div>
</td>
</tr>
<!-- Item 3 -->
<tr>
<td>3</td>
<td>
<div class="d-flex flex-column">
<a href="app-invoice-recurring-add.php" class="fw-bold text-body">Licencja
Oprogramowania Magico ERP</a>
<small class="text-muted">Proforma</small>
</div>
</td>
<td>
<div class="d-flex flex-column">
<span class="fw-semibold">Klinika Stomatologiczna DentaMed</span>
<small class="text-muted">NIP: 554-190-88-21</small>
</div>
</td>
<td>
<span class="text-heading">12 000,00 PLN</span>
</td>
<td>
<span class="fw-bold text-heading">14 760,00 PLN</span>
</td>
<td>
<span class="badge bg-label-warning"><i class="bx bx-calendar me-1"></i> Co 1 rok</span>
<br><small class="text-muted">Bezterminowo</small>
</td>
<td>
<span class="fw-medium text-muted">15.01.2027</span>
<br><small class="text-muted">Ostatnio: 15.01.2026</small>
</td>
<td>
<div class="form-check form-switch m-0" data-bs-toggle="tooltip" title="Włącz / Wyłącz cykl">
<input class="form-check-input" type="checkbox" style="cursor: pointer;">
<span class="badge bg-label-danger ms-1"><i class="bx bx-power-off me-1"></i> OFF</span>
</div>
</td>
<td class="text-center">
<div class="d-flex justify-content-center align-items-center gap-1">
<a href="app-invoice-recurring-add.php"
class="btn btn-icon btn-sm text-secondary" data-bs-toggle="tooltip"
title="Edytuj szablon">
<i class="bx bx-edit-alt fs-5"></i>
</a>
<div class="dropdown">
<button type="button" class="btn p-0 dropdown-toggle hide-arrow"
data-bs-toggle="dropdown">
<i class="bx bx-dots-vertical-rounded fs-5"></i>
</button>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item text-success" href="javascript:void(0);"><i
class="bx bx-power-off me-1"></i> Włącz cykl</a>
<a class="dropdown-item" href="app-invoice-list.php"><i
class="bx bx-history me-1"></i> Historia wygenerowanych
faktur</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger" href="javascript:void(0);"><i
class="bx bx-archive-in me-1"></i> Archiwizuj cykl</a>
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="card-footer d-flex justify-content-between align-items-center">
<span class="text-muted small">Wyświetlono 1-3 z 21 szablonów</span>
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm mb-0">
<li class="page-item disabled"><a class="page-link" href="#"><i
class="bx bx-chevron-left"></i></a></li>
<li class="page-item active"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item"><a class="page-link" href="#"><i class="bx bx-chevron-right"></i></a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<!-- Filter Column Right (Expandable / Collapsible) -->
<div class="col-lg-3" id="invoice-filters-col">
<div class="card sticky-top" style="top: 20px;">
<div class="card-header border-bottom d-flex justify-content-between align-items-center py-3">
<h5 class="card-title mb-0">Filtry cykli</h5>
<button type="button" class="btn-close text-reset small" aria-label="Close" id="close-filter-btn"
style="display:none;"></button>
</div>
<div class="card-body pt-4">
<div class="mb-3">
<label class="form-label">Kontrahent (Nabywca)</label>
<select class="select2 form-select" data-allow-clear="true">
<option value="">Wszyscy kontrahenci</option>
<option value="1">ABC Logistics Sp. z o.o.</option>
<option value="2">Bud-Spec Budownictwo S.A.</option>
<option value="3">Klinika Stomatologiczna DentaMed</option>
<option value="4">MCreo Mateusz Łada</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Status cyklu</label>
<div class="d-flex flex-column gap-2">
<div class="form-check">
<input name="status-radio" class="form-check-input" type="radio" value="" id="statusAll"
checked>
<label class="form-check-label" for="statusAll"> Wszystkie </label>
</div>
<div class="form-check">
<input name="status-radio" class="form-check-input" type="radio" value=""
id="statusActive">
<label class="form-check-label" for="statusActive"> tylko Aktywne </label>
</div>
<div class="form-check">
<input name="status-radio" class="form-check-input" type="radio" value=""
id="statusPaused">
<label class="form-check-label" for="statusPaused"> Wstrzymane (Pauza) </label>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Częstotliwość wystawiania</label>
<select class="form-select">
<option value="">Wszystkie częstotliwości</option>
<option value="1M">Co 1 miesiąc</option>
<option value="3M">Co 3 miesiące (Kwartalnie)</option>
<option value="12M">Co 1 rok (Rocznie)</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Zakres dat najbliższej emisji</label>
<input type="text" class="form-control flatpickr-range"
placeholder="RRRR-MM-DD do RRRR-MM-DD" />
</div>
<div class="mb-3">
<label class="form-label">Tryb automatyzacji</label>
<select class="form-select">
<option value="">Wszystkie tryby</option>
<option value="auto">Automatyczne wystawianie i wysyłka</option>
<option value="draft">Tworzenie jako Szkic</option>
</select>
</div>
<div class="d-flex justify-content-end gap-2 pt-2">
<button class="btn btn-outline-secondary btn-sm">Wyczyść filtry</button>
<button class="btn btn-primary btn-sm">Filtruj</button>
</div>
</div>
</div>
</div>
</div>
</div>
<?php include '../../footer.php'; ?>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Obsługa przełącznika panelu filtrów po prawej stronie
const filterBtn = document.getElementById('filter-toggle-btn');
const filtersCol = document.getElementById('invoice-filters-col');
const listCol = document.getElementById('invoice-list-col');
if (filterBtn && filtersCol && listCol) {
filterBtn.addEventListener('click', function () {
filtersCol.classList.toggle('d-none');
if (filtersCol.classList.contains('d-none')) {
listCol.classList.remove('col-lg-9');
listCol.classList.add('col-lg-12');
} else {
listCol.classList.remove('col-lg-12');
listCol.classList.add('col-lg-9');
}
});
}
// Flatpickr range jeśli obecny
if (typeof flatpickr !== 'undefined') {
document.querySelectorAll('.flatpickr-range').forEach(function (element) {
flatpickr(element, {
mode: 'range',
dateFormat: 'Y-m-d'
});
});
}
});
</script>
</body>
</html>
+18
View File
@@ -121,6 +121,24 @@ include '../../header-invoice.php';
</div>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Rodzaj faktury (KSeF)</label>
<select class="form-select">
<option value="podstawowa" selected>Podstawowa</option>
<option value="zaliczkowa">Zaliczkowa</option>
<option value="rozliczeniowa">Rozliczeniowa</option>
<option value="uproszczona">Uproszczona</option>
<option value="korygujaca">Korygująca</option>
<option value="korygujaca_zaliczkowa">Korygująca fakturę zaliczkową</option>
<option value="korygujaca_rozliczeniowa">Korygująca fakturę rozliczeniową</option>
<option value="podstawowa_pef">Podstawowa PEF</option>
<option value="specjalizowana_pef">Specjalizowana PEF</option>
<option value="korygujaca_pef">Korygująca PEF</option>
<option value="pierwotna_rr">Pierwotna RR</option>
<option value="korygujaca_rr">Korygująca RR</option>
</select>
</div>
<div class="row g-2 mb-3">
<div class="col-md-6">
<label class="form-label fw-bold">Numer faktury</label>
+52 -8
View File
@@ -211,6 +211,26 @@ include '../../header-invoice.php';
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-12">
<label class="form-label">Rodzaj faktury</label>
<select class="form-select">
<option value="podstawowa" selected>Podstawowa</option>
<option value="zaliczkowa">Zaliczkowa</option>
<option value="rozliczeniowa">Rozliczeniowa</option>
<option value="uproszczona">Uproszczona</option>
<option value="korygujaca">Korygująca</option>
<option value="korygujaca_zaliczkowa">Korygująca fakturę zaliczkową</option>
<option value="korygujaca_rozliczeniowa">Korygująca fakturę rozliczeniową</option>
<option value="podstawowa_pef">Podstawowa PEF</option>
<option value="specjalizowana_pef">Specjalizowana PEF</option>
<option value="korygujaca_pef">Korygująca PEF</option>
<option value="pierwotna_rr">Pierwotna RR</option>
<option value="korygujaca_rr">Korygująca RR</option>
</select>
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-md-4">
<label class="form-label">Data wystawienia</label>
@@ -375,8 +395,8 @@ include '../../header-invoice.php';
</td>
<td>1</td>
<td>
<span class="fw-medium">FZ/2026/02/001</span>
<br><small class="text-muted">Hosting i domeny</small>
<span class="fw-medium d-block mb-1">FZ/2026/02/001</span>
<small class="text-muted">Podstawowa | Hosting i domeny</small>
</td>
<td>
<span class="text-heading text-truncate fw-medium">OVH Sp. z o.o.</span>
@@ -431,8 +451,8 @@ include '../../header-invoice.php';
</td>
<td>2</td>
<td>
<span class="fw-medium">FV/TAURON/22/05</span>
<br><small class="text-muted">Energia elektryczna</small>
<span class="fw-medium d-block mb-1">FV/TAURON/22/05</span>
<small class="text-muted">Podstawowa | Energia elektryczna</small>
</td>
<td>
<span class="text-heading text-truncate fw-medium">Tauron Polska</span>
@@ -491,8 +511,8 @@ include '../../header-invoice.php';
</td>
<td>3</td>
<td>
<span class="fw-medium">102/K/2026</span>
<br><small class="text-muted">Materiały biurowe</small>
<span class="fw-medium d-block mb-1">102/K/2026</span>
<small class="text-muted">Korygująca | Materiały biurowe</small>
</td>
<td>
<span class="text-heading text-truncate fw-medium">Papierniczy S.A.</span>
@@ -553,8 +573,8 @@ include '../../header-invoice.php';
</td>
<td>4</td>
<td>
<span class="fw-medium">FV/22/09/2026</span>
<br><small class="text-muted">Usługi programistyczne</small>
<span class="fw-medium d-block mb-1">FV/22/09/2026</span>
<small class="text-muted">Podstawowa | Usługi programistyczne</small>
</td>
<td>
<span class="text-heading text-truncate fw-medium">DevHouse Sp. z o.o.</span>
@@ -648,6 +668,25 @@ include '../../header-invoice.php';
</select>
</div>
<div class="mb-3">
<label class="form-label">Rodzaj faktury</label>
<select class="form-select">
<option value="">Wszystkie</option>
<option value="podstawowa">Podstawowa</option>
<option value="zaliczkowa">Zaliczkowa</option>
<option value="rozliczeniowa">Rozliczeniowa</option>
<option value="uproszczona">Uproszczona</option>
<option value="korygujaca">Korygująca</option>
<option value="korygujaca_zaliczkowa">Korygująca fakturę zaliczkową</option>
<option value="korygujaca_rozliczeniowa">Korygująca fakturę rozliczeniową</option>
<option value="podstawowa_pef">Podstawowa PEF</option>
<option value="specjalizowana_pef">Specjalizowana PEF</option>
<option value="korygujaca_pef">Korygująca PEF</option>
<option value="pierwotna_rr">Pierwotna RR</option>
<option value="korygujaca_rr">Korygująca RR</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Status płatności</label>
<div class="d-flex flex-column gap-2">
@@ -847,6 +886,7 @@ include '../../header-invoice.php';
<div class="d-flex align-items-center gap-3">
<h4 class="modal-title fw-bold mb-0">FZ/2026/02/001</h4>
<span class="badge bg-label-success fs-6">Opłacona</span>
<span class="badge bg-label-secondary fs-6">Podstawowa</span>
</div>
<div class="d-flex align-items-center gap-2">
@@ -922,6 +962,10 @@ include '../../header-invoice.php';
<h6 class="card-title m-0 mb-3"><i class="bx bx-info-circle me-2"></i>Szczegóły</h6>
<table class="table table-borderless table-sm m-0 px-0">
<tbody>
<tr>
<td class="text-muted ps-0 py-1">Rodzaj faktury:</td>
<td class="fw-medium text-end pe-0 py-1">Podstawowa</td>
</tr>
<tr>
<td class="text-muted ps-0 py-1">Data zakupu:</td>
<td class="fw-medium text-end pe-0 py-1">2026-02-15</td>
@@ -0,0 +1,338 @@
<?php
$enablePrototypeComments = true;
include '../../header-invoice.php';
?>
<div class="container-fluid flex-grow-1 container-p-y">
<h4 class="py-3 mb-4">
<span class="text-muted fw-light">Sprzedaż / Ustawienia /</span> Powiadomienia e-mail (Faktury cykliczne)
</h4>
<div class="row">
<!-- Lewa Kolumna: Konfigurator -->
<div class="col-lg-5 mb-4 mb-lg-0">
<!-- Wybór Profilu -->
<div class="card mb-4">
<div class="card-header border-bottom d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">Wybierz Profil Powiadomień</h5>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-label-danger"><i class="bx bx-trash me-1"></i> Usuń profil</button>
<button class="btn btn-sm btn-primary"><i class="bx bx-plus me-1"></i> Nowy profil</button>
</div>
</div>
<div class="card-body pt-4">
<div class="mb-3">
<label class="form-label">Aktualny profil</label>
<select class="form-select select2" id="profile-select">
<option value="1">Standardowy (Termin 30 dni)</option>
<option value="2">Szybki (Termin 3 dni)</option>
<option value="3">Tylko przypomnienia po terminie</option>
</select>
</div>
<div class="alert alert-info d-flex mb-0" role="alert">
<span class="badge badge-center rounded-pill bg-info border-label-info p-3 me-2"><i class="bx bx-info-circle fs-6"></i></span>
<div class="d-flex flex-column ps-1">
<h6 class="alert-heading d-flex align-items-center mb-1">Czym są profile wysyłki?</h6>
<span class="mb-0 fs-6">Profile pozwalają na zgrupowanie reguł wysyłki e-mail. Możesz stworzyć profil "Długi termin" z wieloma przypomnieniami i przypisać go do wybranych faktur cyklicznych, a dla innych użyć profilu "Szybki" tylko z jednym mailem po terminie.</span>
</div>
</div>
</div>
</div>
<!-- Harmonogram wysyłki -->
<div class="card mb-4">
<div class="card-header border-bottom d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">Harmonogram wysyłki</h5>
<button class="btn btn-sm btn-label-primary" data-bs-toggle="modal" data-bs-target="#modalRule"><i class="bx bx-plus me-1"></i> Dodaj regułę</button>
</div>
<div class="card-body pt-4">
<ul class="list-group list-group-flush mb-0">
<li class="list-group-item d-flex justify-content-between align-items-center px-0 pt-0 pb-3" style="cursor: pointer;">
<div class="d-flex align-items-center gap-2">
<span class="badge bg-label-success p-2"><i class="bx bx-check"></i></span>
<div>
<h6 class="mb-0">Wystawienie faktury</h6>
<small class="text-muted">Natychmiast (W dniu dokumentu)</small>
</div>
</div>
<button class="btn btn-icon btn-sm text-secondary" data-bs-toggle="modal" data-bs-target="#modalRule"><i class="bx bx-edit"></i></button>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center px-3 py-3 rounded border border-primary bg-label-primary mb-2 mt-2" style="cursor: pointer;">
<div class="d-flex align-items-center gap-2">
<span class="badge bg-primary p-2 text-white"><i class="bx bx-time"></i></span>
<div>
<h6 class="mb-0 text-primary fw-bold">Przypomnienie przed terminem</h6>
<small class="text-primary">14 dni przed upływem terminu</small>
</div>
</div>
<button class="btn btn-icon btn-sm text-primary" data-bs-toggle="modal" data-bs-target="#modalRule"><i class="bx bx-edit"></i></button>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center px-0 py-3" style="cursor: pointer;">
<div class="d-flex align-items-center gap-2">
<span class="badge bg-label-warning p-2"><i class="bx bx-error"></i></span>
<div>
<h6 class="mb-0">Przypomnienie przed terminem</h6>
<small class="text-muted">3 dni przed upływem terminu</small>
</div>
</div>
<button class="btn btn-icon btn-sm text-secondary" data-bs-toggle="modal" data-bs-target="#modalRule"><i class="bx bx-edit"></i></button>
</li>
</ul>
</div>
</div>
<!-- Treść wiadomości -->
<div class="card border border-primary">
<div class="card-header border-bottom bg-label-primary">
<h5 class="card-title mb-0 text-primary">Treść wiadomości <span class="fw-normal fs-6 d-block mt-1">Edytujesz: <strong>Przypomnienie przed terminem (14 dni)</strong></span></h5>
</div>
<div class="card-body pt-4">
<div class="mb-3">
<label class="form-label">Temat e-maila</label>
<input type="text" id="email-subject" class="form-control" value="Przypomnienie: Zbliża się termin płatności faktury {NUMER_FAKTURY}">
<div class="form-text">Zmienne: {NUMER_FAKTURY}, {NAZWA_KLIENTA}</div>
</div>
<div class="mb-3">
<label class="form-label">Nagłówek powitania</label>
<input type="text" id="email-heading" class="form-control" value="Cześć {IMIE_NABYWCY},">
</div>
<div class="mb-3">
<label class="form-label">Główna treść wiadomości</label>
<textarea id="email-body" class="form-control" rows="4">Przypominamy, że za 14 dni upływa termin płatności za Twoją ostatnią fakturę. W załączniku przesyłamy plik PDF z dokumentem. Będziemy wdzięczni za terminową wpłatę.</textarea>
</div>
<div class="mb-0">
<label class="form-label">Podpis / Stopka</label>
<input type="text" id="email-footer" class="form-control" value="Pozdrawiamy serdecznie,\nZespół TwojaFirma">
</div>
<!-- Zmienne do kliknięcia -->
<div class="mt-4 pt-3 border-top">
<small class="text-muted text-uppercase fw-bold d-block mb-2">Dostępne zmienne (kliknij, aby wstawić)</small>
<div class="d-flex flex-wrap gap-2" id="variable-badges">
<span class="badge bg-label-primary" style="cursor: pointer;" data-var="{NUMER_FAKTURY}">+ {NUMER_FAKTURY}</span>
<span class="badge bg-label-primary" style="cursor: pointer;" data-var="{NAZWA_KLIENTA}">+ {NAZWA_KLIENTA}</span>
<span class="badge bg-label-primary" style="cursor: pointer;" data-var="{IMIE_NABYWCY}">+ {IMIE_NABYWCY}</span>
<span class="badge bg-label-primary" style="cursor: pointer;" data-var="{KWOTA_BRUTTO}">+ {KWOTA_BRUTTO}</span>
<span class="badge bg-label-primary" style="cursor: pointer;" data-var="{TERMIN_PLATNOSCI}">+ {TERMIN_PLATNOSCI}</span>
<span class="badge bg-label-primary" style="cursor: pointer;" data-var="{POZOSTALO_DNI_DO_TERMINU}">+ {POZOSTALO_DNI_DO_TERMINU}</span>
<span class="badge bg-label-primary" style="cursor: pointer;" data-var="{LINK_DO_OPLATY}">+ {LINK_DO_OPLATY}</span>
</div>
<div class="form-text mt-2">Tag zostanie wstawiony w polu, w którym ostatnio pisałeś.</div>
</div>
</div>
</div>
</div>
<!-- Prawa Kolumna: Podgląd Live -->
<div class="col-lg-7">
<div class="card h-100" style="background-color: #f8f9fa;">
<div class="card-header border-bottom bg-white d-flex justify-content-between align-items-center">
<div>
<small class="text-muted d-block mb-1">Podgląd na żywo</small>
<h6 class="mb-0">Temat: <span id="preview-subject" class="fw-normal">Przypomnienie: Zbliża się termin płatności faktury FV/2026/08/12</span></h6>
</div>
<span class="badge bg-label-secondary">Wersja Desktop</span>
</div>
<!-- Email Canvas -->
<div class="card-body d-flex justify-content-center align-items-start pt-5 pb-5 overflow-auto" style="min-height: 600px;">
<div class="email-container bg-white rounded shadow-sm" style="width: 100%; max-width: 600px; overflow: hidden; font-family: Arial, sans-serif;">
<!-- Email Header Logo (Image based on screenshot) -->
<div class="text-center pt-5 pb-3 bg-white">
<div style="background-color: #8ed689; width: 120px; height: 75px; border-radius: 8px; margin: 0 auto; position: relative; display: flex; align-items: center; justify-content: center;">
<!-- Simple illustration placeholder for the green money/face -->
<div style="width: 10px; height: 10px; background-color: #333; border-radius: 50%; position: absolute; left: 35px;"></div>
<div style="width: 10px; height: 10px; background-color: #333; border-radius: 50%; position: absolute; right: 35px;"></div>
<div style="width: 16px; height: 8px; border-bottom: 3px solid #333; border-radius: 50%; position: absolute; top: 40px;"></div>
</div>
</div>
<!-- Email Content Container -->
<div class="p-4 p-md-5 pt-0 bg-white">
<h3 id="preview-heading" class="fw-light mb-4" style="color: #2b5c8f; font-size: 26px;">Szanowni Państwo</h3>
<p id="preview-body" style="color: #4a5568; line-height: 1.6; margin-bottom: 40px; font-size: 15px;">
Dziękujemy za skorzystanie z naszych usług.<br><br>
Uprzejmie informujemy, że w dniu 22 lipca 2026 została wystawiona dla Państwa Faktura nr FV/31/07/2026 na kwotę 160,00 zł. Plik PDF znajduje się w załączniku.
</p>
<!-- ZABLOKOWANY MODUŁ Z FAKTURĄ (LOCKED SUMMARY TABLE) -->
<div class="invoice-summary border-0 rounded bg-lighter p-4 mb-4 text-center" style="border-left: 4px solid #2b5c8f !important;">
<div class="d-flex justify-content-between align-items-center mb-2 text-start">
<span class="text-muted">Typ faktury:</span>
<span class="text-dark">Faktura</span>
</div>
<div class="d-flex justify-content-between align-items-center mb-2 text-start">
<span class="text-muted">Termin płatności:</span>
<span class="text-dark fw-medium">29 lipca 2026</span>
</div>
<div class="d-flex justify-content-between align-items-center mb-3 text-start">
<span class="text-muted">Kwota do zapłaty:</span>
<span class="fw-bold text-dark">160,00 PLN</span>
</div>
<a href="#" class="btn btn-primary w-100 mt-2" style="background-color: #2b5c8f; border-color: #2b5c8f; padding: 10px 20px; text-decoration: none; display: inline-block;">
Zobacz dokument i opłać online
</a>
</div>
<!-- END LOCKED MODULE -->
<p id="preview-footer" style="color: #777; margin-top: 30px; font-size: 0.95em;">
Pozdrawiamy serdecznie,<br>Zespół TwojaFirma
</p>
</div>
<!-- Email Footer App Info -->
<div class="bg-light text-center py-4 px-3 border-top">
<small class="text-muted d-block mb-1" style="font-size: 11px;">Wiadomość wygenerowana automatycznie. Prosimy nie odpowiadać.</small>
<small class="text-muted d-block mb-3" style="font-size: 11px;">Wiadomość wysłana przez firmę TwojaFirma, z siedzibą w Stalowej Woli, al. Jana Pawła II 60F,<br>NIP: 865 256 70 62, REGON: 368428365.</small>
<small class="text-dark fw-bold d-block" style="font-size: 12px;">Magico Software</small>
<small class="text-muted d-block" style="font-size: 11px;">support@magico.pl | +48 665 408 808</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal: Dodaj Regułę -->
<div class="modal fade" id="modalRule" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalRuleTitle">Konfiguracja reguły wysyłki</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col mb-3">
<label class="form-label">Wydarzenie początkowe (Wyzwalacz)</label>
<select class="form-select">
<option value="issue">Wystawienie nowej faktury</option>
<option value="due">Termin płatności faktury</option>
</select>
</div>
</div>
<div class="row g-2">
<div class="col mb-0">
<label class="form-label">Kiedy wysłać e-mail?</label>
<select class="form-select">
<option value="0">Dokładnie w tym dniu</option>
<option value="before">Przed wydarzeniem</option>
<option value="after">Po wydarzeniu</option>
</select>
</div>
<div class="col mb-0">
<label class="form-label">Ile dni?</label>
<input type="number" class="form-control" value="0">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-label-secondary" data-bs-dismiss="modal">Anuluj</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Zapisz regułę</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Inputs
const inputSubject = document.getElementById('email-subject');
const inputHeading = document.getElementById('email-heading');
const inputBody = document.getElementById('email-body');
const inputFooter = document.getElementById('email-footer');
// Previews
const previewSubject = document.getElementById('preview-subject');
const previewHeading = document.getElementById('preview-heading');
const previewBody = document.getElementById('preview-body');
const previewFooter = document.getElementById('preview-footer');
// Mocks for variables
const mockVars = {
'{NUMER_FAKTURY}': 'FV/2026/08/12',
'{NAZWA_KLIENTA}': 'ABC Logistics',
'{IMIE_NABYWCY}': 'Jan',
'{KWOTA_BRUTTO}': '1 845,00 PLN',
'{TERMIN_PLATNOSCI}': '15.08.2026',
'{POZOSTALO_DNI_DO_TERMINU}': '14',
'{LINK_DO_OPLATY}': 'https://magico.pl/pay/123'
};
function replaceVariables(text) {
let result = text;
for (const [key, value] of Object.entries(mockVars)) {
result = result.replace(new RegExp(key, 'g'), value);
}
return result;
}
function updatePreview() {
previewSubject.innerText = replaceVariables(inputSubject.value);
previewHeading.innerText = replaceVariables(inputHeading.value);
// Preserve line breaks in body
const bodyText = replaceVariables(inputBody.value);
previewBody.innerHTML = bodyText.replace(/\n/g, '<br>');
// Preserve line breaks in footer
const footerText = replaceVariables(inputFooter.value);
previewFooter.innerHTML = footerText.replace(/\n/g, '<br>');
}
// Event Listeners
inputSubject.addEventListener('input', updatePreview);
inputHeading.addEventListener('input', updatePreview);
inputBody.addEventListener('input', updatePreview);
inputFooter.addEventListener('input', updatePreview);
// Initial render
updatePreview();
// Variable insertion logic
let lastFocusedInput = inputBody; // default
const allInputs = [inputSubject, inputHeading, inputBody, inputFooter];
allInputs.forEach(input => {
input.addEventListener('focus', function() {
lastFocusedInput = this;
});
});
document.querySelectorAll('#variable-badges .badge').forEach(badge => {
badge.addEventListener('click', function() {
const varText = this.getAttribute('data-var');
if (lastFocusedInput) {
const start = lastFocusedInput.selectionStart;
const end = lastFocusedInput.selectionEnd;
const text = lastFocusedInput.value;
lastFocusedInput.value = text.substring(0, start) + varText + text.substring(end);
lastFocusedInput.selectionStart = lastFocusedInput.selectionEnd = start + varText.length;
lastFocusedInput.focus();
updatePreview();
}
});
});
});
</script>
<?php include '../../footer.php'; ?>
+12 -2
View File
@@ -108,7 +108,7 @@ $currentPage = basename($_SERVER['PHP_SELF']);
</li>
<li
class="menu-item <?php echo ($currentPage == 'app-invoice-list.php' || $currentPage == 'app-purchase-list.php' || $currentPage == 'app-purchase-calendar.php' || $currentPage == 'app-products.php') ? 'active open' : ''; ?>">
class="menu-item <?php echo ($currentPage == 'app-invoice-list.php' || $currentPage == 'app-invoice-recurring-list.php' || $currentPage == 'app-invoice-recurring-add.php' || $currentPage == 'app-purchase-list.php' || $currentPage == 'app-purchase-calendar.php' || $currentPage == 'app-products.php') ? 'active open' : ''; ?>">
<a href="javascript:void(0);" class="menu-link menu-toggle">
<i class="menu-icon tf-icons bx bx-cart-alt"></i>
<div class="text-truncate" data-i18n="Faktury i sprzedaż">Faktury i sprzedaż</div>
@@ -119,6 +119,11 @@ $currentPage = basename($_SERVER['PHP_SELF']);
<div class="text-truncate" data-i18n="Faktury sprzedaż">Faktury sprzedaż</div>
</a>
</li>
<li class="menu-item <?php echo ($currentPage == 'app-invoice-recurring-list.php' || $currentPage == 'app-invoice-recurring-add.php') ? 'active' : ''; ?>">
<a href="app-invoice-recurring-list.php" class="menu-link">
<div class="text-truncate" data-i18n="Faktury cykliczne">Faktury cykliczne</div>
</a>
</li>
<li class="menu-item <?php echo ($currentPage == 'app-purchase-list.php') ? 'active' : ''; ?>">
<a href="app-purchase-list.php" class="menu-link">
<div class="text-truncate" data-i18n="Faktury zakupy">Faktury zakupy</div>
@@ -151,7 +156,7 @@ $currentPage = basename($_SERVER['PHP_SELF']);
</li>
<li
class="menu-item <?php echo ($currentPage == 'sys-export.php' || $currentPage == 'sys-dictionaries.php' || $currentPage == 'sys-settings.php' || $currentPage == 'sys-logs.php') ? 'active open' : ''; ?>">
class="menu-item <?php echo ($currentPage == 'sys-export.php' || $currentPage == 'sys-dictionaries.php' || $currentPage == 'sys-settings.php' || $currentPage == 'app-settings-notifications.php' || $currentPage == 'sys-logs.php') ? 'active open' : ''; ?>">
<a href="javascript:void(0);" class="menu-link menu-toggle">
<i class="menu-icon tf-icons bx bx-cog"></i>
<div class="text-truncate" data-i18n="System">System</div>
@@ -172,6 +177,11 @@ $currentPage = basename($_SERVER['PHP_SELF']);
<div class="text-truncate" data-i18n="Ustawienia">Ustawienia</div>
</a>
</li>
<li class="menu-item <?php echo ($currentPage == 'app-settings-notifications.php') ? 'active' : ''; ?>">
<a href="app-settings-notifications.php" class="menu-link">
<div class="text-truncate" data-i18n="Powiadomienia (Cykliczne)">Powiadomienia (Cykliczne)</div>
</a>
</li>
<li class="menu-item <?php echo ($currentPage == 'sys-logs.php') ? 'active' : ''; ?>">
<a href="sys-logs.php" class="menu-link">
<div class="text-truncate" data-i18n="Logi zdarzeń">Logi zdarzeń</div>
@@ -0,0 +1,342 @@
:root {
--primary-color: #696cff;
--primary-hover: #5f61e6;
--bg-light: #f5f5f9;
--text-dark: #32475c;
--text-muted: #8592a3;
--white: #ffffff;
--border-color: #e4e6f4;
--shadow-sm: 0 0.125rem 0.25rem rgba(161, 172, 184, 0.2);
--shadow-md: 0 0.25rem 0.5rem rgba(161, 172, 184, 0.3);
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
}
body {
background-color: var(--bg-light);
color: var(--text-dark);
font-family: 'Public Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
max-width: 500px;
margin: 0 auto;
position: relative;
box-shadow: 0 0 20px rgba(0,0,0,0.05);
min-height: 100vh;
}
html {
background-color: #e9ecef;
}
/* Utilities */
.text-primary-custom { color: var(--primary-color) !important; }
.bg-primary-custom { background-color: var(--primary-color) !important; color: var(--white); }
/* Buttons */
.btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
border-radius: var(--radius-md);
padding: 0.6rem 1.2rem;
font-weight: 600;
}
.btn-primary:hover, .btn-primary:focus {
background-color: var(--primary-hover);
border-color: var(--primary-hover);
}
/* Cards & Tiles */
.card-custom {
background: var(--white);
border: none;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card-custom:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
/* Form inputs */
.form-control {
border-radius: var(--radius-md);
padding: 0.75rem 1rem;
border: 1px solid var(--border-color);
}
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.25rem rgba(105, 108, 255, 0.1);
}
/* Topbar */
.app-topbar {
background: var(--white);
box-shadow: var(--shadow-sm);
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
z-index: 1000;
}
.app-topbar .app-title {
font-weight: 700;
font-size: 1.1rem;
margin: 0;
color: var(--text-dark);
}
.user-avatar {
width: 38px;
height: 38px;
border-radius: 50%;
background-color: rgba(105, 108, 255, 0.1);
color: var(--primary-color);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
}
/* RCP Hero Button */
.btn-rcp {
border-radius: var(--radius-lg);
padding: 1.25rem;
font-size: 1.1rem;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
box-shadow: 0 0.5rem 1rem rgba(105, 108, 255, 0.2);
}
/* Grid Tiles */
.tile {
text-decoration: none;
color: var(--text-dark);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1.5rem 1rem;
text-align: center;
height: 100%;
}
.tile .tile-icon {
font-size: 2.5rem;
color: var(--primary-color);
margin-bottom: 0.75rem;
background: rgba(105, 108, 255, 0.1);
width: 64px;
height: 64px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.tile .tile-title {
font-weight: 600;
font-size: 0.95rem;
margin: 0;
}
/* Select Company List */
.company-list-item {
display: flex;
align-items: center;
padding: 1rem;
border-bottom: 1px solid var(--border-color);
text-decoration: none;
color: var(--text-dark);
transition: background-color 0.2s;
}
.company-list-item:hover {
background-color: var(--bg-light);
}
.company-list-item:last-child {
border-bottom: none;
}
.company-logo-placeholder {
width: 48px;
height: 48px;
border-radius: var(--radius-md);
background-color: var(--primary-color);
color: var(--white);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
font-weight: bold;
margin-right: 1rem;
}
/* Location Loader & Animations */
.location-loader {
position: relative;
width: 80px;
height: 80px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
}
.location-loader .pulse {
position: absolute;
width: 100%;
height: 100%;
background-color: rgba(105, 108, 255, 0.4);
border-radius: 50%;
z-index: 1;
animation: pulse-animation 1.5s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
}
@keyframes pulse-animation {
0% { transform: scale(0.5); opacity: 1; }
100% { transform: scale(1.5); opacity: 0; }
}
@keyframes bounce-pin {
0%, 100% { transform: translateY(0) rotate(0deg); }
25% { transform: translateY(-15px) rotate(-10deg); }
50% { transform: translateY(0) rotate(0deg); }
75% { transform: translateY(-5px) rotate(10deg); }
}
.bouncing-pin {
animation: bounce-pin 1.2s infinite ease-in-out;
display: inline-block;
}
/* Text Fade Transition */
.fade-transition {
transition: opacity 0.4s ease-in-out;
}
.fade-out {
opacity: 0 !important;
}
/* Calendar Grid */
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 5px;
margin-bottom: 1rem;
}
.calendar-header-day {
text-align: center;
font-size: 0.7rem;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
padding-bottom: 5px;
}
.calendar-day {
aspect-ratio: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: var(--radius-md);
background-color: var(--white);
font-size: 0.85rem;
font-weight: 600;
color: var(--text-dark);
position: relative;
box-shadow: 0 2px 4px rgba(161, 172, 184, 0.1);
cursor: pointer;
transition: transform 0.2s, background-color 0.2s;
}
.calendar-day.empty {
background-color: transparent;
box-shadow: none;
cursor: default;
}
.calendar-day:active:not(.empty) {
transform: scale(0.95);
}
.calendar-day.active {
background-color: var(--primary-color);
color: var(--white);
}
.calendar-day .shift-dot {
width: 6px;
height: 6px;
border-radius: 50%;
margin-top: 3px;
}
.calendar-day .shift-dot.blue { background-color: var(--primary-color); }
.calendar-day .shift-dot.green { background-color: #71dd37; }
.calendar-day .shift-dot.purple { background-color: #e83e8c; }
.calendar-day.active .shift-dot { background-color: var(--white) !important; }
/* Custom Badges (Sneat style) */
.bg-label-primary {
background-color: rgba(105, 108, 255, 0.16) !important;
color: var(--primary-color) !important;
}
.bg-label-success {
background-color: rgba(113, 221, 55, 0.16) !important;
color: #71dd37 !important;
}
.bg-label-warning {
background-color: rgba(255, 171, 0, 0.16) !important;
color: #ffab00 !important;
}
.bg-label-danger {
background-color: rgba(255, 62, 29, 0.16) !important;
color: #ff3e1d !important;
}
.bg-label-secondary {
background-color: rgba(133, 146, 163, 0.16) !important;
color: #8592a3 !important;
}
/* Custom Nav Pills */
.nav-pills .nav-link {
background-color: var(--white);
color: var(--text-muted);
transition: all 0.2s ease-in-out;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
background-color: var(--primary-color) !important;
color: var(--white) !important;
box-shadow: 0 0.125rem 0.25rem rgba(105, 108, 255, 0.4) !important;
}
/* Floating Action Button (FAB) */
.fab-wrapper {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
width: 100%;
max-width: 500px; /* same as body max-width */
display: flex;
justify-content: flex-end;
padding-right: 20px;
z-index: 1050;
pointer-events: none; /* Allows clicking through the wrapper */
}
.fab-btn {
pointer-events: auto;
width: 60px;
height: 60px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
box-shadow: 0 0.5rem 1rem rgba(105, 108, 255, 0.4);
transition: transform 0.2s ease, box-shadow 0.2s ease;
padding: 0;
}
.fab-btn:active {
transform: scale(0.92);
}
+12
View File
@@ -0,0 +1,12 @@
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- Magico Feedback JS -->
<script>
const MAGICO_BASE_URL = <?= json_encode($basePath); ?>;
</script>
<?php if (!empty($enablePrototypeComments) && $enablePrototypeComments): ?>
<script src="<?= $basePath ?>/assets/js/comments.js"></script>
<?php endif; ?>
</body>
</html>
+33
View File
@@ -0,0 +1,33 @@
<?php
$basePath = '';
if ($_SERVER['HTTP_HOST'] === 'localhost' || $_SERVER['HTTP_HOST'] === '127.0.0.1') {
$basePath = '/magico-prototype';
}
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Pracownik Magico</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Boxicons -->
<link href="https://cdn.jsdelivr.net/npm/boxicons@2.1.4/css/boxicons.min.css" rel="stylesheet">
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/style.css">
<!-- Magico Feedback CSS -->
<?php if (!empty($enablePrototypeComments) && $enablePrototypeComments): ?>
<link rel="stylesheet" href="<?= $basePath ?>/assets/css/comments.css">
<?php endif; ?>
</head>
<body>
+357
View File
@@ -0,0 +1,357 @@
<?php
$enablePrototypeComments = true;
include 'header.php';
?>
<div class="app-topbar">
<div class="d-flex align-items-center">
<div class="d-flex justify-content-center align-items-center bg-primary-custom rounded-circle me-2" style="width: 32px; height: 32px;">
<i class='bx bx-cube-alt text-white'></i>
</div>
<h1 class="app-title">Acme Corp</h1>
</div>
<div class="d-flex align-items-center gap-3">
<!-- Powiadomienia -->
<div class="dropdown">
<div class="position-relative" style="cursor: pointer;" data-bs-toggle="dropdown" aria-expanded="false">
<i class='bx bx-bell fs-4 text-muted'></i>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger border border-white" style="font-size: 0.65rem; padding: 0.3em 0.45em;">
1
</span>
</div>
<div class="dropdown-menu dropdown-menu-end p-0 border-0 mt-2" style="border-radius: 16px; width: 340px; box-shadow: 0 0.5rem 1.5rem rgba(105, 108, 255, 0.15); overflow: hidden; z-index: 1050; margin-right: -40px;">
<div class="p-3 border-bottom d-flex justify-content-between align-items-center" style="background-color: #fcfcfd;">
<h6 class="mb-0 fw-bold" style="color: #566A7F;">Powiadomienia</h6>
<span class="badge bg-label-danger rounded-pill">1 nowe</span>
</div>
<div class="list-group list-group-flush" style="max-height: 350px; overflow-y: auto;">
<!-- Nieprzeczytane -->
<a href="vacations.php" class="list-group-item list-group-item-action d-flex align-items-start p-3 border-0" style="background-color: rgba(105, 108, 255, 0.03); border-bottom: 1px solid rgba(0,0,0,0.03) !important;">
<div class="d-flex justify-content-center align-items-center rounded-circle flex-shrink-0 me-3" style="width: 40px; height: 40px; background-color: rgba(255, 62, 29, 0.1); color: #ff3e1d;">
<i class='bx bx-x-circle fs-5'></i>
</div>
<div class="flex-grow-1">
<h6 class="mb-1 fw-bold" style="font-size: 0.9rem; color: #566A7F;">Wniosek odrzucony</h6>
<p class="mb-1 small text-muted" style="line-height: 1.4;">Kierownik odrzucił Twój wniosek o urlop na żądanie (05 Wrz).</p>
<small class="text-primary-custom fw-semibold" style="font-size: 0.75rem;">10 min temu</small>
</div>
<div class="flex-shrink-0 ms-2 mt-2">
<div style="width: 8px; height: 8px; background-color: #696cff; border-radius: 50%;"></div>
</div>
</a>
<!-- Przeczytane -->
<a href="vacations.php" class="list-group-item list-group-item-action d-flex align-items-start p-3 border-0">
<div class="d-flex justify-content-center align-items-center rounded-circle flex-shrink-0 me-3" style="width: 40px; height: 40px; background-color: rgba(113, 221, 55, 0.1); color: #71dd37;">
<i class='bx bx-check-circle fs-5'></i>
</div>
<div class="flex-grow-1">
<h6 class="mb-1 fw-semibold" style="font-size: 0.9rem; color: #566A7F;">Wniosek zaakceptowany</h6>
<p class="mb-1 small text-muted" style="line-height: 1.4;">Twój urlop wypoczynkowy (12-16 Sie) został pomyślnie zatwierdzony.</p>
<small class="text-muted" style="font-size: 0.75rem;">Wczoraj</small>
</div>
</a>
</div>
<div class="p-3 border-top text-center" style="background-color: #fcfcfd;">
<button class="btn btn-primary btn-sm w-100 fw-bold" style="border-radius: 8px;">Zobacz wszystkie</button>
</div>
</div>
</div>
<div class="user-avatar shadow-sm">
<i class='bx bx-user'></i>
</div>
</div>
</div>
<div class="container py-4">
<!-- Hero / RCP Button -->
<div class="mb-4" id="rcp-container">
<h5 class="fw-bold mb-3">Dzień dobry, Marcin 👋</h5>
<!-- Stan 1: Rozpocznij -->
<div id="rcp-start-state">
<button class="btn btn-primary w-100 btn-rcp" data-bs-toggle="modal" data-bs-target="#rcpModal">
<i class='bx bx-fingerprint fs-3'></i>
Rozpocznij pracę
</button>
<div class="text-center mt-2">
<small class="text-muted">Zarejestruj wejście do firmy</small>
</div>
</div>
<!-- Stan 2: W trakcie pracy -->
<div id="rcp-active-state" class="d-none">
<div class="d-flex gap-2">
<button class="btn btn-outline-warning w-50 btn-rcp" style="padding: 1rem; font-size: 1rem;"
data-bs-toggle="modal" data-bs-target="#breakModal">
<i class='bx bx-coffee fs-4'></i>
<span>Przerwa</span>
</button>
<button class="btn btn-danger w-50 btn-rcp" style="padding: 1rem; font-size: 1rem;"
data-bs-toggle="modal" data-bs-target="#endWorkModal">
<i class='bx bx-log-out fs-4'></i>
Zakończ
</button>
</div>
<div class="text-center mt-2">
<small class="text-success fw-bold"><i class='bx bx-check-circle'></i> Pracujesz od 08:00</small>
</div>
</div>
</div>
<!-- Grid -->
<div class="row g-3">
<div class="col-6">
<a href="schedule.php" class="card-custom tile">
<div class="tile-icon">
<i class='bx bx-calendar-event'></i>
</div>
<h2 class="tile-title">Grafik Pracy</h2>
</a>
</div>
<div class="col-6">
<a href="vacations.php" class="card-custom tile">
<div class="tile-icon text-success" style="background: rgba(113, 221, 55, 0.1);">
<i class='bx bx-sun'></i>
</div>
<h2 class="tile-title mt-2">Urlopy</h2>
</a>
</div>
</div>
</div>
<!-- Modal RCP -->
<div class="modal fade" id="rcpModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow" style="border-radius: var(--radius-lg);">
<div class="modal-body p-4 text-center">
<!-- Krok 1: Potwierdzenie -->
<div id="rcp-step-confirm">
<div class="mb-3">
<i class='bx bx-map-pin text-primary' style="font-size: 4rem;"></i>
</div>
<h4 class="fw-bold mb-2">Rozpocząć pracę?</h4>
<p class="text-muted mb-4">System zweryfikuje Twoją lokalizację GPS z dozwoloną strefą dla tej
firmy.</p>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-secondary w-50"
data-bs-dismiss="modal">Anuluj</button>
<button type="button" class="btn btn-primary w-50" id="btn-confirm-start">Tak,
rozpocznij</button>
</div>
</div>
<!-- Krok 2: Ładowanie (Lokalizacja) -->
<div id="rcp-step-loading" class="d-none py-3">
<div class="location-loader mb-4">
<div class="pulse"></div>
<i class='bx bxs-map-pin text-primary bouncing-pin'
style="font-size: 3.5rem; position: relative; z-index: 2;"></i>
</div>
<h5 class="fw-bold mb-1">Weryfikacja lokalizacji...</h5>
<p class="text-muted small fade-transition" id="loading-text-dynamic" style="min-height: 20px;">
Rozglądamy się za Tobą 🛰️</p>
</div>
<!-- Krok 3: Sukces -->
<div id="rcp-step-success" class="d-none py-3">
<div class="mb-3">
<i class='bx bx-check-circle text-success' style="font-size: 4rem;"></i>
</div>
<h4 class="fw-bold mb-1">Zalogowano pomyślnie!</h4>
<p class="text-muted">Twoja praca została rozpoczęta.</p>
</div>
</div>
</div>
</div>
</div>
<!-- Modal Przerwy -->
<div class="modal fade" id="breakModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow" style="border-radius: var(--radius-lg);">
<div class="modal-body p-4 text-center">
<div class="mb-3">
<i class='bx bx-coffee text-warning' style="font-size: 4rem;" id="break-modal-icon"></i>
</div>
<h4 class="fw-bold mb-2" id="break-modal-title">Rozpocząć przerwę?</h4>
<p class="text-muted mb-4" id="break-modal-desc">Twój czas pracy zostanie wstrzymany na czas trwania
przerwy.</p>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-secondary w-50" data-bs-dismiss="modal">Anuluj</button>
<button type="button" class="btn btn-warning w-50 text-white" id="btn-confirm-break"
data-bs-dismiss="modal">Tak, rozpocznij</button>
</div>
</div>
</div>
</div>
</div>
<!-- Modal Zakończenia Pracy -->
<div class="modal fade" id="endWorkModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow" style="border-radius: var(--radius-lg);">
<div class="modal-body p-4 text-center">
<div class="mb-3">
<i class='bx bx-log-out-circle text-danger' style="font-size: 4rem;"></i>
</div>
<h4 class="fw-bold mb-2">Zakończyć pracę?</h4>
<p class="text-muted mb-4">Twój dzisiejszy czas pracy zostanie podsumowany i zapisany.</p>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-secondary w-50" data-bs-dismiss="modal">Anuluj</button>
<button type="button" class="btn btn-danger w-50" id="btn-confirm-end" data-bs-dismiss="modal">Tak,
zakończ</button>
</div>
</div>
</div>
</div>
</div>
<?php include 'footer.php'; ?>
<script>
document.addEventListener('DOMContentLoaded', function () {
const btnConfirm = document.getElementById('btn-confirm-start');
const stepConfirm = document.getElementById('rcp-step-confirm');
const stepLoading = document.getElementById('rcp-step-loading');
const stepSuccess = document.getElementById('rcp-step-success');
const stateStart = document.getElementById('rcp-start-state');
const stateActive = document.getElementById('rcp-active-state');
const rcpModalEl = document.getElementById('rcpModal');
let rcpModal;
if (typeof bootstrap !== 'undefined') {
rcpModal = new bootstrap.Modal(rcpModalEl);
}
// Po zamknięciu modala, resetuj widoki, jeśli był w trakcie itp
rcpModalEl.addEventListener('hidden.bs.modal', event => {
// Resetujemy tylko jeśli nie jesteśmy w trakcie pokazywania sukcesu
if (stepSuccess.classList.contains('d-none')) {
stepConfirm.classList.remove('d-none');
stepLoading.classList.add('d-none');
stepSuccess.classList.add('d-none');
}
});
btnConfirm.addEventListener('click', function () {
// Pokaż ładowanie
stepConfirm.classList.add('d-none');
stepLoading.classList.remove('d-none');
const textElement = document.getElementById('loading-text-dynamic');
const messages = [
"Rozglądamy się za Tobą 🛰️",
"Sprawdzamy współrzędne GPS 🛰️",
"Kalibrujemy pozycję 📍",
"Łączymy się z satelitą 📡",
"Weryfikujemy strefę pracy 🗺️",
"Analizujemy zasięg sygnału 📶",
"Pobieramy dane lokalizacyjne ⏳",
"Jeszcze chwilę... prawie gotowe! 🚀"
];
let msgIndex = 0;
textElement.textContent = messages[0];
const intervalId = setInterval(() => {
// Rozpocznij zanikanie
textElement.classList.add('fade-out');
setTimeout(() => {
// Po zaniknięciu (400ms), zmień tekst i pokaż ponownie
msgIndex = (msgIndex + 1) % messages.length;
textElement.textContent = messages[msgIndex];
textElement.classList.remove('fade-out');
}, 400);
}, 2500);
// Symulacja weryfikacji GPS (Wydłużona do 8.5 sekund)
setTimeout(() => {
clearInterval(intervalId);
stepLoading.classList.add('d-none');
stepSuccess.classList.remove('d-none');
// Zamknij modal po 1.5 sekundy od sukcesu i zmień stan
setTimeout(() => {
if (rcpModal) rcpModal.hide();
stateStart.classList.add('d-none');
stateActive.classList.remove('d-none');
// Przywróć stan początkowy modala do ponownego użycia w przyszłości
setTimeout(() => {
stepConfirm.classList.remove('d-none');
stepLoading.classList.add('d-none');
stepSuccess.classList.add('d-none');
}, 500);
}, 1500);
}, 8500);
});
// Obsługa przerwy
const btnConfirmBreak = document.getElementById('btn-confirm-break');
const breakBtnElement = document.querySelector('button[data-bs-target="#breakModal"]');
let isBreakActive = false;
btnConfirmBreak.addEventListener('click', function () {
if (!isBreakActive) {
// Rozpocznij przerwę
isBreakActive = true;
breakBtnElement.innerHTML = "<i class='bx bx-play-circle fs-4'></i><span>Wznów</span>";
breakBtnElement.classList.replace('btn-outline-warning', 'btn-warning');
breakBtnElement.classList.add('text-white');
// Zmiana tekstów modala na "Powrót z przerwy"
document.getElementById('break-modal-icon').className = "bx bx-play-circle text-warning";
document.getElementById('break-modal-title').textContent = "Zakończyć przerwę?";
document.getElementById('break-modal-desc').textContent = "Twój czas pracy zostanie wznowiony.";
btnConfirmBreak.textContent = "Tak, wznów";
} else {
// Zakończ przerwę
isBreakActive = false;
breakBtnElement.innerHTML = "<i class='bx bx-coffee fs-4'></i><span>Przerwa</span>";
breakBtnElement.classList.replace('btn-warning', 'btn-outline-warning');
breakBtnElement.classList.remove('text-white');
// Zmiana tekstów modala z powrotem na "Rozpocznij przerwę"
document.getElementById('break-modal-icon').className = "bx bx-coffee text-warning";
document.getElementById('break-modal-title').textContent = "Rozpocząć przerwę?";
document.getElementById('break-modal-desc').textContent = "Twój czas pracy zostanie wstrzymany na czas trwania przerwy.";
btnConfirmBreak.textContent = "Tak, rozpocznij";
}
});
// Obsługa zakończenia pracy
const btnConfirmEnd = document.getElementById('btn-confirm-end');
btnConfirmEnd.addEventListener('click', function () {
// Wróć do ekranu startowego
stateActive.classList.add('d-none');
stateStart.classList.remove('d-none');
// Resetuj stan przerwy jeśli była aktywna
if (isBreakActive) {
isBreakActive = false;
breakBtnElement.innerHTML = "<i class='bx bx-coffee fs-4'></i><span>Przerwa</span>";
breakBtnElement.classList.replace('btn-warning', 'btn-outline-warning');
breakBtnElement.classList.remove('text-white');
document.getElementById('break-modal-icon').className = "bx bx-coffee text-warning";
document.getElementById('break-modal-title').textContent = "Rozpocząć przerwę?";
document.getElementById('break-modal-desc').textContent = "Twój czas pracy zostanie wstrzymany na czas trwania przerwy.";
btnConfirmBreak.textContent = "Tak, rozpocznij";
}
});
});
</script>
+48
View File
@@ -0,0 +1,48 @@
<?php
$enablePrototypeComments = true;
include 'header.php';
?>
<div class="container d-flex flex-column justify-content-center align-items-center min-vh-100 py-5">
<div class="text-center mb-5">
<div class="d-inline-flex align-items-center justify-content-center bg-primary-custom rounded-circle mb-3"
style="width: 80px; height: 80px;">
<i class='bx bx-cube-alt text-white' style="font-size: 3rem;"></i>
</div>
<h2 class="fw-bold mb-1">Pracownik Magico</h2>
<p class="text-muted">Zaloguj się do swojego konta</p>
</div>
<div class="card-custom w-100 p-4" style="max-width: 400px;">
<form action="select-company.php" method="GET">
<div class="mb-3">
<label for="email" class="form-label text-muted small fw-semibold text-uppercase">Adres e-mail</label>
<input type="email" class="form-control" id="email" placeholder="jan.kowalski@firma.pl"
value="jan@magico.pl">
</div>
<div class="mb-4">
<div class="d-flex justify-content-between">
<label for="password" class="form-label text-muted small fw-semibold text-uppercase">Hasło</label>
<a href="#" class="text-primary-custom small text-decoration-none">Zapomniałeś hasła?</a>
</div>
<div class="input-group input-group-merge">
<input type="password" id="password" class="form-control"
placeholder="&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;"
value="fdsaf45tfsgtyr">
</div>
</div>
<div class="mb-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="remember-me" checked>
<label class="form-check-label" for="remember-me"> Zapamiętaj mnie </label>
</div>
</div>
<button class="btn btn-primary w-100" type="submit">Zaloguj się</button>
</form>
</div>
</div>
<?php include 'footer.php'; ?>
@@ -0,0 +1,126 @@
<?php
$enablePrototypeComments = true;
include 'header.php';
?>
<!-- Główny Topbar -->
<div class="app-topbar">
<div class="d-flex align-items-center">
<div class="d-flex justify-content-center align-items-center bg-primary-custom rounded-circle me-2" style="width: 32px; height: 32px;">
<i class='bx bx-cube-alt text-white'></i>
</div>
<h1 class="app-title">Acme Corp</h1>
</div>
<div class="user-avatar shadow-sm">
<i class='bx bx-user'></i>
</div>
</div>
<!-- Pasek tytułowy sekcji -->
<div class="container pt-4">
<div class="d-flex justify-content-between align-items-center pb-3 mb-4" style="border-bottom: 1px solid var(--border-color);">
<div class="d-flex align-items-center">
<a href="home.php" class="text-dark me-2 d-flex align-items-center justify-content-center" style="width: 32px; height: 32px; background: rgba(0,0,0,0.05); border-radius: 50%;">
<i class='bx bx-arrow-back fs-4'></i>
</a>
<h2 class="fw-bold mb-0" style="font-size: 1.25rem;">Mój Grafik</h2>
</div>
<!-- Zmieniona ikona na widok listy -->
<a href="schedule.php" class="text-primary-custom d-flex align-items-center justify-content-center" style="width: 36px; height: 36px; background: rgba(105, 108, 255, 0.1); border-radius: 8px;">
<i class='bx bx-list-ul fs-4'></i>
</a>
</div>
</div>
<div class="container pb-4">
<!-- Przełącznik miesiąca -->
<div class="d-flex justify-content-between align-items-center mb-3">
<button class="btn btn-sm btn-link text-dark text-decoration-none"><i class='bx bx-chevron-left fs-4'></i></button>
<span class="fw-bold">Sierpień 2026</span>
<button class="btn btn-sm btn-link text-dark text-decoration-none"><i class='bx bx-chevron-right fs-4'></i></button>
</div>
<!-- Siatka Kalendarza -->
<div class="calendar-grid">
<div class="calendar-header-day">Pn</div>
<div class="calendar-header-day">Wt</div>
<div class="calendar-header-day">Śr</div>
<div class="calendar-header-day">Cz</div>
<div class="calendar-header-day">Pt</div>
<div class="calendar-header-day">So</div>
<div class="calendar-header-day">Nd</div>
<!-- Tydzień 1 (zaczynamy od pustych dni) -->
<div class="calendar-day empty"></div>
<div class="calendar-day empty"></div>
<div class="calendar-day empty"></div>
<div class="calendar-day empty"></div>
<div class="calendar-day empty"></div>
<div class="calendar-day text-muted" style="opacity: 0.5;">1</div>
<div class="calendar-day text-muted" style="opacity: 0.5;">2</div>
<!-- Tydzień 2 -->
<div class="calendar-day">3<div class="shift-dot blue"></div></div>
<div class="calendar-day">4<div class="shift-dot green"></div></div>
<div class="calendar-day">5</div>
<div class="calendar-day">6<div class="shift-dot blue"></div></div>
<div class="calendar-day">7<div class="shift-dot blue"></div></div>
<div class="calendar-day text-muted" style="opacity: 0.5;">8</div>
<div class="calendar-day text-muted" style="opacity: 0.5;">9</div>
<!-- Tydzień 3 (Aktywny tydzień) -->
<div class="calendar-day">10</div>
<div class="calendar-day">11<div class="shift-dot green"></div></div>
<div class="calendar-day active shadow-md">12<div class="shift-dot blue"></div></div>
<div class="calendar-day">13<div class="shift-dot green"></div></div>
<div class="calendar-day">14</div>
<div class="calendar-day text-muted" style="opacity: 0.5;">15</div>
<div class="calendar-day text-muted" style="opacity: 0.5;">16</div>
<!-- Tydzień 4 -->
<div class="calendar-day">17<div class="shift-dot blue"></div></div>
<div class="calendar-day">18<div class="shift-dot blue"></div></div>
<div class="calendar-day">19<div class="shift-dot green"></div></div>
<div class="calendar-day">20</div>
<div class="calendar-day">21<div class="shift-dot blue"></div></div>
<div class="calendar-day text-muted" style="opacity: 0.5;">22</div>
<div class="calendar-day text-muted" style="opacity: 0.5;">23</div>
<!-- Tydzień 5 -->
<div class="calendar-day">24</div>
<div class="calendar-day">25<div class="shift-dot blue"></div></div>
<div class="calendar-day">26<div class="shift-dot green"></div></div>
<div class="calendar-day">27<div class="shift-dot green"></div></div>
<div class="calendar-day">28</div>
<div class="calendar-day text-muted" style="opacity: 0.5;">29</div>
<div class="calendar-day text-muted" style="opacity: 0.5;">30</div>
<!-- Ostatni dzień -->
<div class="calendar-day">31<div class="shift-dot blue"></div></div>
<div class="calendar-day empty"></div>
<div class="calendar-day empty"></div>
<div class="calendar-day empty"></div>
<div class="calendar-day empty"></div>
<div class="calendar-day empty"></div>
<div class="calendar-day empty"></div>
</div>
<!-- Szczegóły dla wybranego dnia -->
<div class="mt-4">
<h6 class="fw-bold mb-3 text-muted text-uppercase small">Wybrany dzień: 12 Sierpnia</h6>
<div class="card-custom shift-card" style="border-left: 4px solid var(--primary-color);">
<div class="p-2 px-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<div>
<h5 class="fw-bold mb-0" style="font-size: 1.1rem;">08:00 - 16:00</h5>
<span class="text-primary-custom fw-semibold" style="font-size: 0.75rem;">Specjalista ds. Obsługi</span>
</div>
<span class="badge bg-label-primary">8h</span>
</div>
</div>
</div>
</div>
</div>
<?php include 'footer.php'; ?>
+299
View File
@@ -0,0 +1,299 @@
<?php
$enablePrototypeComments = true;
include 'header.php';
?>
<!-- Główny Topbar (taki sam jak w home.php) -->
<div class="app-topbar">
<div class="d-flex align-items-center">
<div class="d-flex justify-content-center align-items-center bg-primary-custom rounded-circle me-2" style="width: 32px; height: 32px;">
<i class='bx bx-cube-alt text-white'></i>
</div>
<h1 class="app-title">Acme Corp</h1>
</div>
<div class="d-flex align-items-center gap-3">
<!-- Powiadomienia -->
<div class="dropdown">
<div class="position-relative" style="cursor: pointer;" data-bs-toggle="dropdown" aria-expanded="false">
<i class='bx bx-bell fs-4 text-muted'></i>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger border border-white" style="font-size: 0.65rem; padding: 0.3em 0.45em;">
1
</span>
</div>
<div class="dropdown-menu dropdown-menu-end p-0 border-0 mt-2" style="border-radius: 16px; width: 340px; box-shadow: 0 0.5rem 1.5rem rgba(105, 108, 255, 0.15); overflow: hidden; z-index: 1050; margin-right: -40px;">
<div class="p-3 border-bottom d-flex justify-content-between align-items-center" style="background-color: #fcfcfd;">
<h6 class="mb-0 fw-bold" style="color: #566A7F;">Powiadomienia</h6>
<span class="badge bg-label-danger rounded-pill">1 nowe</span>
</div>
<div class="list-group list-group-flush" style="max-height: 350px; overflow-y: auto;">
<!-- Nieprzeczytane -->
<a href="vacations.php" class="list-group-item list-group-item-action d-flex align-items-start p-3 border-0" style="background-color: rgba(105, 108, 255, 0.03); border-bottom: 1px solid rgba(0,0,0,0.03) !important;">
<div class="d-flex justify-content-center align-items-center rounded-circle flex-shrink-0 me-3" style="width: 40px; height: 40px; background-color: rgba(255, 62, 29, 0.1); color: #ff3e1d;">
<i class='bx bx-x-circle fs-5'></i>
</div>
<div class="flex-grow-1">
<h6 class="mb-1 fw-bold" style="font-size: 0.9rem; color: #566A7F;">Wniosek odrzucony</h6>
<p class="mb-1 small text-muted" style="line-height: 1.4;">Kierownik odrzucił Twój wniosek o urlop na żądanie (05 Wrz).</p>
<small class="text-primary-custom fw-semibold" style="font-size: 0.75rem;">10 min temu</small>
</div>
<div class="flex-shrink-0 ms-2 mt-2">
<div style="width: 8px; height: 8px; background-color: #696cff; border-radius: 50%;"></div>
</div>
</a>
<!-- Przeczytane -->
<a href="vacations.php" class="list-group-item list-group-item-action d-flex align-items-start p-3 border-0">
<div class="d-flex justify-content-center align-items-center rounded-circle flex-shrink-0 me-3" style="width: 40px; height: 40px; background-color: rgba(113, 221, 55, 0.1); color: #71dd37;">
<i class='bx bx-check-circle fs-5'></i>
</div>
<div class="flex-grow-1">
<h6 class="mb-1 fw-semibold" style="font-size: 0.9rem; color: #566A7F;">Wniosek zaakceptowany</h6>
<p class="mb-1 small text-muted" style="line-height: 1.4;">Twój urlop wypoczynkowy (12-16 Sie) został pomyślnie zatwierdzony.</p>
<small class="text-muted" style="font-size: 0.75rem;">Wczoraj</small>
</div>
</a>
</div>
<div class="p-3 border-top text-center" style="background-color: #fcfcfd;">
<button class="btn btn-primary btn-sm w-100 fw-bold" style="border-radius: 8px;">Zobacz wszystkie</button>
</div>
</div>
</div>
<div class="user-avatar shadow-sm">
<i class='bx bx-user'></i>
</div>
</div>
</div>
<!-- Pasek tytułowy sekcji -->
<div class="container pt-4">
<div class="d-flex justify-content-between align-items-center pb-3" style="border-bottom: 1px solid var(--border-color);">
<div class="d-flex align-items-center">
<a href="home.php" class="text-dark me-2 d-flex align-items-center justify-content-center" style="width: 32px; height: 32px; background: rgba(0,0,0,0.05); border-radius: 50%;">
<i class='bx bx-arrow-back fs-4'></i>
</a>
<h2 class="fw-bold mb-0" style="font-size: 1.25rem;">Mój Grafik</h2>
</div>
<div class="d-flex align-items-center gap-2">
<!-- Ikona filtrowania (Toggle zaawansowanych) -->
<button class="btn btn-sm text-primary-custom d-flex align-items-center justify-content-center p-0" style="width: 36px; height: 36px; background: rgba(105, 108, 255, 0.1); border-radius: 8px; border: none;" data-bs-toggle="collapse" data-bs-target="#advancedFilters" aria-expanded="false">
<i class='bx bx-filter fs-4'></i>
</button>
<a href="schedule-calendar.php" class="text-primary-custom d-flex align-items-center justify-content-center p-0 text-decoration-none" style="width: 36px; height: 36px; background: rgba(105, 108, 255, 0.1); border-radius: 8px;">
<i class='bx bx-calendar fs-4'></i>
</a>
</div>
</div>
<!-- Szybki wybór okresu pod spodem -->
<div class="mb-4 pt-3 d-flex justify-content-between align-items-center">
<h6 class="mb-0 text-muted small text-uppercase fw-bold">Wybierz okres</h6>
<select class="form-select form-select-sm border-0 bg-white shadow-sm fw-semibold text-primary-custom" style="border-radius: 8px; cursor: pointer; width: auto; padding: 0.4rem 2rem 0.4rem 0.8rem; box-shadow: 0 0.125rem 0.25rem rgba(161, 172, 184, 0.15) !important;">
<optgroup label="Tygodnie">
<option value="week_current">Bieżący tydzień</option>
<option value="week_next">Następny tydzień</option>
<option value="week_prev">Poprzedni tydzień</option>
</optgroup>
<optgroup label="Miesiące">
<option value="month_current" selected>Bieżący miesiąc</option>
<option value="month_next">Następny miesiąc</option>
<option value="month_prev">Poprzedni miesiąc</option>
</optgroup>
</select>
</div>
<!-- Zaawansowane filtry (ukryte domyślnie, otwierane przez ikonkę filtra) -->
<div class="collapse mb-4" id="advancedFilters">
<div class="card border-0 shadow-sm" style="border-radius: 16px; border: 1px solid rgba(105, 108, 255, 0.2) !important;">
<div class="card-body p-3 pb-1">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0 fw-bold d-flex align-items-center" style="color: #696cff; font-size: 0.95rem;">
<i class='bx bx-slider-alt me-1 fs-5'></i> Własny zakres dat
</h6>
<button type="button" class="btn-close" style="font-size: 0.7rem; box-shadow: none;" data-bs-toggle="collapse" data-bs-target="#advancedFilters" aria-label="Zamknij"></button>
</div>
<!-- Data od -->
<label class="form-label fw-medium" style="font-size: 0.85rem; color: #566A7F;">Data od</label>
<div class="input-group mb-3 dropdown">
<span class="input-group-text bg-white text-muted" style="border-radius: 8px 0 0 8px; border-color: #d9dee3;"><i class='bx bx-calendar'></i></span>
<input type="text" class="form-control bg-white dropdown-toggle" value="2024-08-12" readonly data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false" style="border-radius: 0 8px 8px 0; border-color: #d9dee3; cursor: pointer;">
<!-- SneatDatePicker Mock -->
<div class="dropdown-menu p-3 shadow border-0" style="border-radius: 8px; min-width: 300px; z-index: 1050; margin-top: 4px !important;">
<div class="d-flex justify-content-between align-items-center mb-3">
<a href="#" class="btn btn-sm btn-icon text-secondary" style="padding: 4px;"><i class='bx bx-chevron-left fs-4'></i></a>
<div class="fw-bold d-flex align-items-center gap-1" style="color: #566A7F; font-size: 1rem;">
Sierpień 2024
<div class="d-flex flex-column ms-1">
<i class='bx bx-chevron-up' style="font-size: 1rem; line-height: 0.5; cursor: pointer; color: #566a7f;"></i>
<i class='bx bx-chevron-down' style="font-size: 1rem; line-height: 0.5; cursor: pointer; color: #566a7f;"></i>
</div>
</div>
<a href="#" class="btn btn-sm btn-icon text-secondary" style="padding: 4px;"><i class='bx bx-chevron-right fs-4'></i></a>
</div>
<div class="d-flex mb-2">
<div class="text-center fw-semibold" style="width: 14.28%; font-size: 0.75rem; color: #a1acb8;">Pn</div><div class="text-center fw-semibold" style="width: 14.28%; font-size: 0.75rem; color: #a1acb8;">Wt</div><div class="text-center fw-semibold" style="width: 14.28%; font-size: 0.75rem; color: #a1acb8;">Śr</div><div class="text-center fw-semibold" style="width: 14.28%; font-size: 0.75rem; color: #a1acb8;">Cz</div><div class="text-center fw-semibold" style="width: 14.28%; font-size: 0.75rem; color: #a1acb8;">Pt</div><div class="text-center fw-semibold" style="width: 14.28%; font-size: 0.75rem; color: #a1acb8;">Sb</div><div class="text-center fw-semibold" style="width: 14.28%; font-size: 0.75rem; color: #a1acb8;">Nd</div>
</div>
<div class="d-flex flex-wrap" style="row-gap: 8px;">
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #c7cdd4; text-align: center;">29</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #c7cdd4; text-align: center;">30</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #c7cdd4; text-align: center;">31</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">1</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">2</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">3</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">4</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">5</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">6</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">7</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">8</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">9</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">10</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">11</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; background-color: #696cff; color: #fff; text-align: center; border-radius: 6px; font-weight: 600; box-shadow: 0 2px 4px rgba(105, 108, 255, 0.4);">12</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 30px; border: 1px solid #696cff; color: #566A7F; text-align: center; border-radius: 6px;">13</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">14</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">15</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">16</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">17</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">18</div></div>
</div>
</div>
</div>
<!-- Data do -->
<label class="form-label fw-medium" style="font-size: 0.85rem; color: #566A7F;">Data do</label>
<div class="input-group mb-4 dropdown">
<span class="input-group-text bg-white text-muted" style="border-radius: 8px 0 0 8px; border-color: #d9dee3;"><i class='bx bx-calendar'></i></span>
<input type="text" class="form-control bg-white dropdown-toggle" value="2024-08-18" readonly data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false" style="border-radius: 0 8px 8px 0; border-color: #d9dee3; cursor: pointer;">
<!-- SneatDatePicker Mock -->
<div class="dropdown-menu p-3 shadow border-0" style="border-radius: 8px; min-width: 300px; z-index: 1050; margin-top: 4px !important;">
<div class="d-flex justify-content-between align-items-center mb-3">
<a href="#" class="btn btn-sm btn-icon text-secondary" style="padding: 4px;"><i class='bx bx-chevron-left fs-4'></i></a>
<div class="fw-bold d-flex align-items-center gap-1" style="color: #566A7F; font-size: 1rem;">
Sierpień 2024
<div class="d-flex flex-column ms-1">
<i class='bx bx-chevron-up' style="font-size: 1rem; line-height: 0.5; cursor: pointer; color: #566a7f;"></i>
<i class='bx bx-chevron-down' style="font-size: 1rem; line-height: 0.5; cursor: pointer; color: #566a7f;"></i>
</div>
</div>
<a href="#" class="btn btn-sm btn-icon text-secondary" style="padding: 4px;"><i class='bx bx-chevron-right fs-4'></i></a>
</div>
<div class="d-flex mb-2">
<div class="text-center fw-semibold" style="width: 14.28%; font-size: 0.75rem; color: #a1acb8;">Pn</div><div class="text-center fw-semibold" style="width: 14.28%; font-size: 0.75rem; color: #a1acb8;">Wt</div><div class="text-center fw-semibold" style="width: 14.28%; font-size: 0.75rem; color: #a1acb8;">Śr</div><div class="text-center fw-semibold" style="width: 14.28%; font-size: 0.75rem; color: #a1acb8;">Cz</div><div class="text-center fw-semibold" style="width: 14.28%; font-size: 0.75rem; color: #a1acb8;">Pt</div><div class="text-center fw-semibold" style="width: 14.28%; font-size: 0.75rem; color: #a1acb8;">Sb</div><div class="text-center fw-semibold" style="width: 14.28%; font-size: 0.75rem; color: #a1acb8;">Nd</div>
</div>
<div class="d-flex flex-wrap" style="row-gap: 8px;">
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #c7cdd4; text-align: center;">29</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #c7cdd4; text-align: center;">30</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #c7cdd4; text-align: center;">31</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">1</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">2</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">3</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">4</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">5</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">6</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">7</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">8</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">9</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">10</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">11</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">12</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 30px; border: 1px solid #696cff; color: #566A7F; text-align: center; border-radius: 6px;">13</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">14</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">15</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">16</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; color: #566A7F; text-align: center;">17</div></div>
<div class="d-flex justify-content-center align-items-center" style="width: 14.28%;"><div style="width: 32px; height: 32px; line-height: 32px; background-color: #696cff; color: #fff; text-align: center; border-radius: 6px; font-weight: 600; box-shadow: 0 2px 4px rgba(105, 108, 255, 0.4);">18</div></div>
</div>
</div>
</div>
<button class="btn w-100 mb-3 fw-medium text-white" style="border-radius: 8px; background-color: #696cff; border-color: #696cff;">Szukaj</button>
<div class="text-start">
<a href="#" class="text-decoration-none fw-medium" style="font-size: 0.85rem; color: #566A7F;">Wyczyść filtry i daty</a>
</div>
</div>
</div>
</div>
</div>
<div class="container pb-4">
<!-- Dzień: Poniedziałek -->
<div class="schedule-day mb-3">
<h6 class="fw-bold mb-2 text-muted text-uppercase" style="font-size: 0.7rem;">Poniedziałek, 12 Sierpnia</h6>
<div class="card-custom shift-card" style="border-left: 4px solid var(--primary-color);">
<div class="p-2 px-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="fw-bold mb-0" style="font-size: 1.1rem;">08:00 - 16:00</h5>
<span class="text-primary-custom fw-semibold" style="font-size: 0.75rem;">Specjalista ds. Obsługi</span>
</div>
<span class="badge bg-label-primary">8h</span>
</div>
</div>
</div>
</div>
<!-- Dzień: Wtorek -->
<div class="schedule-day mb-3">
<h6 class="fw-bold mb-2 text-muted text-uppercase" style="font-size: 0.7rem;">Wtorek, 13 Sierpnia</h6>
<div class="card-custom shift-card" style="border-left: 4px solid #71dd37;">
<div class="p-2 px-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<div>
<h5 class="fw-bold mb-0" style="font-size: 1.1rem;">10:00 - 18:00</h5>
<span class="text-success fw-semibold" style="font-size: 0.75rem;">Zastępca Kierownika</span>
</div>
<span class="badge bg-label-success" style="background: rgba(113, 221, 55, 0.1); color: #71dd37;">8h</span>
</div>
<!-- Notatka od managera (bardzo kompaktowa) -->
<div class="bg-light p-2 rounded text-muted d-flex align-items-start gap-2" style="font-size: 0.75rem;">
<i class='bx bx-message-square-detail text-warning mt-1'></i>
<div>
<strong class="text-dark">Anna M.:</strong> Pamiętaj o odbiorze dużej dostawy przed południem.
</div>
</div>
</div>
</div>
</div>
<!-- Dzień: Środa -->
<div class="schedule-day mb-3">
<h6 class="fw-bold mb-2 text-muted text-uppercase" style="font-size: 0.7rem;">Środa, 14 Sierpnia</h6>
<div class="card-custom bg-transparent border border-dashed border-secondary opacity-50" style="box-shadow: none;">
<div class="p-2 text-center">
<span class="text-muted fw-semibold" style="font-size: 0.8rem;">Dzień wolny</span>
</div>
</div>
</div>
<!-- Dzień: Czwartek -->
<div class="schedule-day mb-3">
<h6 class="fw-bold mb-2 text-muted text-uppercase" style="font-size: 0.7rem;">Czwartek, 15 Sierpnia</h6>
<div class="card-custom shift-card" style="border-left: 4px solid var(--primary-color);">
<div class="p-2 px-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="fw-bold mb-0" style="font-size: 1.1rem;">08:00 - 16:00</h5>
<span class="text-primary-custom fw-semibold" style="font-size: 0.75rem;">Specjalista ds. Obsługi</span>
</div>
<span class="badge bg-label-primary">8h</span>
</div>
</div>
</div>
</div>
</div>
<?php include 'footer.php'; ?>
@@ -0,0 +1,58 @@
<?php
$enablePrototypeComments = true;
include 'header.php';
?>
<div class="app-topbar">
<div class="d-flex align-items-center">
<a href="index.php" class="text-dark me-2"><i class='bx bx-arrow-back fs-4'></i></a>
<h1 class="app-title">Wybierz firmę</h1>
</div>
</div>
<div class="container py-4">
<p class="text-muted mb-4">Wybierz firmę, dla której chcesz dzisiaj pracować, aby kontynuować logowanie.</p>
<div class="card-custom overflow-hidden">
<a href="home.php" class="company-list-item">
<div class="company-logo-placeholder bg-primary-custom">
A
</div>
<div class="flex-grow-1">
<h6 class="mb-0 fw-bold">Acme Corp Sp. z o.o.</h6>
<small class="text-muted">NIP: 1234567890</small>
</div>
<div>
<i class='bx bx-chevron-right fs-3 text-muted'></i>
</div>
</a>
<a href="home.php" class="company-list-item">
<div class="company-logo-placeholder bg-dark text-white">
G
</div>
<div class="flex-grow-1">
<h6 class="mb-0 fw-bold">Global Industries</h6>
<small class="text-muted">NIP: 9876543210</small>
</div>
<div>
<i class='bx bx-chevron-right fs-3 text-muted'></i>
</div>
</a>
<a href="home.php" class="company-list-item">
<div class="company-logo-placeholder bg-info text-white">
M
</div>
<div class="flex-grow-1">
<h6 class="mb-0 fw-bold">Magico Sp. z o.o.</h6>
<small class="text-muted">Oddział Główny</small>
</div>
<div>
<i class='bx bx-chevron-right fs-3 text-muted'></i>
</div>
</a>
</div>
</div>
<?php include 'footer.php'; ?>
+379
View File
@@ -0,0 +1,379 @@
<?php
$enablePrototypeComments = true;
include 'header.php';
?>
<!-- Główny Topbar -->
<div class="app-topbar">
<div class="d-flex align-items-center">
<div class="d-flex justify-content-center align-items-center bg-primary-custom rounded-circle me-2" style="width: 32px; height: 32px;">
<i class='bx bx-cube-alt text-white'></i>
</div>
<h1 class="app-title">Acme Corp</h1>
</div>
<div class="d-flex align-items-center gap-3">
<!-- Powiadomienia -->
<div class="dropdown">
<div class="position-relative" style="cursor: pointer;" data-bs-toggle="dropdown" aria-expanded="false">
<i class='bx bx-bell fs-4 text-muted'></i>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger border border-white" style="font-size: 0.65rem; padding: 0.3em 0.45em;">
1
</span>
</div>
<div class="dropdown-menu dropdown-menu-end p-0 border-0 mt-2" style="border-radius: 16px; width: 340px; box-shadow: 0 0.5rem 1.5rem rgba(105, 108, 255, 0.15); overflow: hidden; z-index: 1050; margin-right: -40px;">
<div class="p-3 border-bottom d-flex justify-content-between align-items-center" style="background-color: #fcfcfd;">
<h6 class="mb-0 fw-bold" style="color: #566A7F;">Powiadomienia</h6>
<span class="badge bg-label-danger rounded-pill">1 nowe</span>
</div>
<div class="list-group list-group-flush" style="max-height: 350px; overflow-y: auto;">
<!-- Nieprzeczytane -->
<a href="vacations.php" class="list-group-item list-group-item-action d-flex align-items-start p-3 border-0" style="background-color: rgba(105, 108, 255, 0.03); border-bottom: 1px solid rgba(0,0,0,0.03) !important;">
<div class="d-flex justify-content-center align-items-center rounded-circle flex-shrink-0 me-3" style="width: 40px; height: 40px; background-color: rgba(255, 62, 29, 0.1); color: #ff3e1d;">
<i class='bx bx-x-circle fs-5'></i>
</div>
<div class="flex-grow-1">
<h6 class="mb-1 fw-bold" style="font-size: 0.9rem; color: #566A7F;">Wniosek odrzucony</h6>
<p class="mb-1 small text-muted" style="line-height: 1.4;">Kierownik odrzucił Twój wniosek o urlop na żądanie (05 Wrz).</p>
<small class="text-primary-custom fw-semibold" style="font-size: 0.75rem;">10 min temu</small>
</div>
<div class="flex-shrink-0 ms-2 mt-2">
<div style="width: 8px; height: 8px; background-color: #696cff; border-radius: 50%;"></div>
</div>
</a>
<!-- Przeczytane -->
<a href="vacations.php" class="list-group-item list-group-item-action d-flex align-items-start p-3 border-0">
<div class="d-flex justify-content-center align-items-center rounded-circle flex-shrink-0 me-3" style="width: 40px; height: 40px; background-color: rgba(113, 221, 55, 0.1); color: #71dd37;">
<i class='bx bx-check-circle fs-5'></i>
</div>
<div class="flex-grow-1">
<h6 class="mb-1 fw-semibold" style="font-size: 0.9rem; color: #566A7F;">Wniosek zaakceptowany</h6>
<p class="mb-1 small text-muted" style="line-height: 1.4;">Twój urlop wypoczynkowy (12-16 Sie) został pomyślnie zatwierdzony.</p>
<small class="text-muted" style="font-size: 0.75rem;">Wczoraj</small>
</div>
</a>
</div>
<div class="p-3 border-top text-center" style="background-color: #fcfcfd;">
<button class="btn btn-primary btn-sm w-100 fw-bold" style="border-radius: 8px;">Zobacz wszystkie</button>
</div>
</div>
</div>
<div class="user-avatar shadow-sm">
<i class='bx bx-user'></i>
</div>
</div>
</div>
<!-- Pasek tytułowy sekcji -->
<div class="container pt-4">
<div class="d-flex justify-content-between align-items-center pb-3 mb-4" style="border-bottom: 1px solid var(--border-color);">
<div class="d-flex align-items-center">
<a href="home.php" class="text-dark me-2 d-flex align-items-center justify-content-center" style="width: 32px; height: 32px; background: rgba(0,0,0,0.05); border-radius: 50%;">
<i class='bx bx-arrow-back fs-4'></i>
</a>
<h2 class="fw-bold mb-0" style="font-size: 1.25rem;">Moje Urlopy</h2>
</div>
</div>
</div>
<div class="container pb-4">
<!-- Statystyki Urlopowe (Dashboard Widget) -->
<div class="card border-0 shadow-sm mb-4" style="border-radius: 16px;">
<div class="card-body p-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="d-flex align-items-center">
<div class="d-flex justify-content-center align-items-center me-2" style="width: 32px; height: 32px; border-radius: 8px; background-color: rgba(105, 108, 255, 0.1);">
<i class='bx bx-pie-chart-alt-2' style="color: #696cff; font-size: 1.2rem;"></i>
</div>
<h6 class="fw-bold mb-0" style="color: #566A7F; font-size: 1.05rem;">Pula urlopowa</h6>
</div>
<select class="form-select form-select-sm border-0 bg-light text-muted fw-semibold" style="width: auto; border-radius: 8px; cursor: pointer;">
<option value="2024" selected>Rok: 2024</option>
<option value="2023">Rok: 2023</option>
</select>
</div>
<div class="d-flex justify-content-between align-items-end mb-2 mt-1">
<div>
<span class="text-muted small d-block mb-1">Dostępne do wykorzystania</span>
<h3 class="mb-0 fw-bold" style="color: #696cff;">14 <span class="fs-6 fw-semibold text-muted">z 26 dni</span></h3>
</div>
</div>
<!-- Progress Bar -->
<div class="progress mb-2 mt-3" style="height: 10px; border-radius: 10px; background-color: rgba(105, 108, 255, 0.15);">
<div class="progress-bar" role="progressbar" style="width: 46%; background-color: #696cff; border-radius: 10px;" aria-valuenow="12" aria-valuemin="0" aria-valuemax="26"></div>
</div>
<div class="d-flex justify-content-between text-muted" style="font-size: 0.8rem;">
<span class="fw-medium">Wykorzystano: 12 dni</span>
<span class="fw-medium">Zaległy: 0 dni</span>
</div>
</div>
</div>
<!-- Tabs: Nadchodzące / Historia -->
<ul class="nav nav-pills nav-justified mb-4" id="vacationTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active fw-bold shadow-sm" id="upcoming-tab" data-bs-toggle="pill" data-bs-target="#upcoming" type="button" role="tab" aria-controls="upcoming" aria-selected="true" style="border-radius: 8px;">Nadchodzące</button>
</li>
<li class="nav-item ms-2" role="presentation">
<button class="nav-link fw-bold shadow-sm" id="history-tab" data-bs-toggle="pill" data-bs-target="#history" type="button" role="tab" aria-controls="history" aria-selected="false" style="border-radius: 8px;">Historia</button>
</li>
</ul>
<div class="tab-content p-0 m-0 border-0" id="vacationTabsContent">
<!-- ZAKŁADKA 1: Nadchodzące -->
<div class="tab-pane fade show active" id="upcoming" role="tabpanel" aria-labelledby="upcoming-tab">
<!-- Wniosek 1: Zaakceptowany -->
<div class="card-custom mb-3 border-0 shadow-sm" style="border-left: 4px solid #71dd37 !important;">
<div class="p-3">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<span class="badge bg-label-success mb-1">Zaakceptowany</span>
<h5 class="fw-bold mb-0" style="font-size: 1.1rem;">Urlop wypoczynkowy</h5>
</div>
<div class="text-end">
<span class="fw-bold text-dark fs-5">5</span>
<span class="text-muted small d-block" style="margin-top: -5px;">dni</span>
</div>
</div>
<div class="d-flex align-items-center text-muted small mt-3">
<i class='bx bx-calendar me-2 fs-5 text-primary-custom'></i>
<span class="fw-semibold">12 Sierpnia 2026 - 16 Sierpnia 2026</span>
</div>
</div>
</div>
<!-- Wniosek 2: Oczekujący -->
<div class="card-custom mb-3 border-0 shadow-sm" style="border-left: 4px solid #8592a3 !important;">
<div class="p-3">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<span class="badge bg-label-secondary mb-1">Oczekuje na akceptację</span>
<h5 class="fw-bold mb-0" style="font-size: 1.1rem;">Urlop wypoczynkowy</h5>
</div>
<div class="text-end">
<span class="fw-bold text-dark fs-5">2</span>
<span class="text-muted small d-block" style="margin-top: -5px;">dni</span>
</div>
</div>
<div class="d-flex align-items-center text-muted small mt-3">
<i class='bx bx-calendar me-2 fs-5 text-primary-custom'></i>
<span class="fw-semibold">27 Sierpnia 2026 - 28 Sierpnia 2026</span>
</div>
</div>
</div>
<!-- Wniosek 3: Odrzucony -->
<div class="card-custom mb-3 border-0 shadow-sm" style="border-left: 4px solid #ff3e1d !important;">
<div class="p-3">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<span class="badge bg-label-danger mb-1">Odrzucony</span>
<h5 class="fw-bold mb-0" style="font-size: 1.1rem;">Urlop na żądanie</h5>
</div>
<div class="text-end">
<span class="fw-bold text-dark fs-5">1</span>
<span class="text-muted small d-block" style="margin-top: -5px;">dzień</span>
</div>
</div>
<div class="d-flex align-items-center text-muted small mt-3 mb-2">
<i class='bx bx-calendar me-2 fs-5 text-primary-custom'></i>
<span class="fw-semibold">05 Września 2026</span>
</div>
<div class="bg-light p-2 rounded text-muted small d-flex align-items-start gap-2 mt-2">
<i class='bx bx-info-circle text-danger mt-1'></i>
<div>
<strong class="text-dark">Kierownik:</strong> Niestety w tym terminie mamy inwentaryzację, brak możliwości udzielenia urlopu.
</div>
</div>
</div>
</div>
</div>
<!-- ZAKŁADKA 2: Historia -->
<div class="tab-pane fade" id="history" role="tabpanel" aria-labelledby="history-tab">
<!-- FILTRY HISTORII -->
<div class="d-flex justify-content-between align-items-center mb-4 gap-2">
<select class="form-select bg-white shadow-sm border-0" style="border-radius: 8px; color: #566A7F; font-weight: 500;">
<option value="2024" selected>Rok: 2024</option>
<option value="2023">Rok: 2023</option>
<option value="2022">Rok: 2022</option>
</select>
<select class="form-select bg-white shadow-sm border-0" style="border-radius: 8px; color: #566A7F; font-weight: 500;">
<option value="all" selected>Status: Wszystkie</option>
<option value="accepted">Wykorzystane</option>
<option value="rejected">Odrzucone</option>
</select>
</div>
<!-- Historia: Wykorzystany -->
<div class="card-custom mb-3 border-0 shadow-sm" style="border-left: 4px solid #d9dee3 !important;">
<div class="p-3">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<span class="badge bg-label-secondary mb-1">Wykorzystany</span>
<h5 class="fw-bold mb-0 text-muted" style="font-size: 1.1rem;">Urlop wypoczynkowy</h5>
</div>
<div class="text-end text-muted">
<span class="fw-bold fs-5">14</span>
<span class="small d-block" style="margin-top: -5px;">dni</span>
</div>
</div>
<div class="d-flex align-items-center text-muted small mt-3">
<i class='bx bx-calendar me-2 fs-5'></i>
<span>01 Lipca 2024 - 14 Lipca 2024</span>
</div>
</div>
</div>
<!-- Historia: Odrzucony -->
<div class="card-custom mb-3 border-0 shadow-sm" style="border-left: 4px solid #d9dee3 !important; opacity: 0.8;">
<div class="p-3">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<span class="badge bg-label-danger mb-1 opacity-75">Odrzucony</span>
<h5 class="fw-bold mb-0 text-muted" style="font-size: 1.1rem;">Urlop na żądanie</h5>
</div>
<div class="text-end text-muted">
<span class="fw-bold fs-5">1</span>
<span class="small d-block" style="margin-top: -5px;">dzień</span>
</div>
</div>
<div class="d-flex align-items-center text-muted small mt-3">
<i class='bx bx-calendar me-2 fs-5'></i>
<span>15 Maja 2024</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal: Nowy Wniosek -->
<div class="modal fade" id="vacationModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content border-0 shadow" style="border-radius: var(--radius-lg);">
<div class="modal-header border-bottom-0 pb-0">
<h5 class="modal-title fw-bold">Nowy wniosek o urlop</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="vacation-form">
<div class="mb-3 text-start">
<label class="form-label fw-semibold">Początek urlopu</label>
<div class="input-group input-group-merge">
<span class="input-group-text"><i class="bx bx-calendar"></i></span>
<input type="text" id="date-start" class="form-control datepicker" placeholder="Wybierz datę początkową">
</div>
</div>
<div class="mb-3 text-start">
<label class="form-label fw-semibold">Koniec urlopu</label>
<div class="input-group input-group-merge">
<span class="input-group-text"><i class="bx bx-calendar"></i></span>
<input type="text" id="date-end" class="form-control datepicker" placeholder="Wybierz datę końcową">
</div>
</div>
<div class="mb-3 text-start">
<label class="form-label fw-semibold">Powód</label>
<select class="form-select">
<option value="2">Urlop wypoczynkowy</option>
<option value="4">Zwolnienie - opieka</option>
<option value="3">Zwolnienie lekarskie</option>
<option value="9">Służba wojskowa</option>
<option value="8" selected>Urlop bezpłatny</option>
<option value="5">Urlop macierzyński</option>
<option value="7">Urlop rehabilitacyjny</option>
<option value="6">Urlop wychowawczy</option>
</select>
</div>
<div class="mb-4 text-start">
<label class="form-label fw-semibold">Dodatkowe informacje</label>
<textarea class="form-control" rows="3" placeholder="Opcjonalna uwaga do przełożonego..."></textarea>
</div>
<button type="button" class="btn btn-primary w-100 py-3 fw-bold fs-6" id="btn-submit-vacation">
Wyślij wniosek
</button>
</form>
</div>
</div>
</div>
</div>
<!-- Flatpickr CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<!-- Flatpickr Theme (optional, fits well) -->
<link rel="stylesheet" type="text/css" href="https://npmcdn.com/flatpickr/dist/themes/airbnb.css">
<!-- Floating Action Button (FAB) dla nowego wniosku -->
<div class="fab-wrapper">
<button class="btn btn-primary fab-btn" data-bs-toggle="modal" data-bs-target="#vacationModal" aria-label="Nowy wniosek o urlop">
<i class='bx bx-plus'></i>
</button>
</div>
<?php include 'footer.php'; ?>
<!-- Flatpickr JS -->
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script src="https://npmcdn.com/flatpickr/dist/l10n/pl.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Inicjalizacja datepickera (Flatpickr) z polskim językiem
flatpickr(".datepicker", {
locale: "pl",
dateFormat: "Y-m-d",
disableMobile: "true" // Pozwala na ładny natywny wygląd flatpickr na mobilkach
});
// Symulacja wysyłania
const btnSubmit = document.getElementById('btn-submit-vacation');
if (btnSubmit) {
btnSubmit.addEventListener('click', function() {
const originalText = this.innerHTML;
this.innerHTML = "<i class='bx bx-loader-alt bx-spin me-2'></i> Wysyłanie...";
this.disabled = true;
setTimeout(() => {
this.innerHTML = "<i class='bx bx-check me-2'></i> Wysłano pomyślnie!";
this.classList.replace('btn-primary', 'btn-success');
setTimeout(() => {
const modalEl = document.getElementById('vacationModal');
const modal = bootstrap.Modal.getInstance(modalEl);
if (modal) modal.hide();
// Reset buttona
this.innerHTML = originalText;
this.classList.replace('btn-success', 'btn-primary');
this.disabled = false;
}, 1500);
}, 1500);
});
}
});
</script>
+196
View File
@@ -0,0 +1,196 @@
Opis Biznesowy Aplikacji: telemetry.magico (Telemetry Manager)
1. Wstęp i Cel Projektu
telemetry.magico to nowoczesna, przemysłowa platforma klasy IoT (Internet of Things) oraz czasu rzeczywistego (Real-Time Monitoring), zintegrowana z ekosystemem biznesowym magico.pro.
Głównym celem aplikacji jest agregacja, wizualizacja oraz analiza danych telemetrycznych pochodzących z rozproszonych urządzeń pomiarowych, sensorów i sterowników przemysłowych. Kluczowym założeniem biznesowym jest pełna agnostyczność danych (generyczność) system projektowany jest w taki sposób, aby rodzaj monitorowanej infrastruktury (np. poziom gazu w zbiornikach, mikroklimat w kurnikach, parametry pracy maszyn HVAC) nie wpływał na strukturę rdzenia aplikacji, a jedynie na warstwę prezentacji danych i konfiguracji urządzeń.
2. Grupa Docelowa i Zastosowanie Biznesowe
Aplikacja odpowiada na potrzeby przedsiębiorstw zarządzających rozproszoną infrastrukturą techniczną. Przykładowe scenariusze wdrożeniowe obejmują:
Sektor Energetyczny / Gazowy (Pierwsze wdrożenie): Monitoring poziomu napełnienia zbiorników LPG/CNG, ciśnienia oraz detekcja anomalii i wycieków. Optimale planowanie logistyki dostaw surowca.
Agrotechnika (Smart Farming): Monitorowanie temperatury, wilgotności, poziomu CO2 czy zużycia paszy w obiektach inwentarskich (kurniki, chłodnie).
Smart Building & Industry: Monitorowanie parametrów pracy maszyn, zużycia energii elektrycznej, statusów online/offline urządzeń produkcyjnych.
3. Kluczowe Korzyści Biznesowe (Value Proposition)
Redukcja kosztów operacyjnych: Automatyczna kontrola stanu urządzeń eliminuje konieczność fizycznych i rutynowych inspekcji obiektów przez pracowników.
Zapobieganie awariom i przestojom: System wczesnego ostrzegania informuje o stanach krytycznych (np. drastyczny spadek ciśnienia, odcięcie zasilania), zanim wpłyną one na ciągłość biznesową.
Optymalizacja logistyczna: Na podstawie trendów zużycia (np. gazu) system pozwala precyzyjnie planować terminy kolejnych dostaw lub serwisów.
Skalowalność środowiska magico.pro: Moduł telemetryczny wymienia dane z innymi aplikacjami w ekosystemie (np. generowanie automatycznych ticketów w helpdesk.magico przy awarii sensora lub fakturowanie zużycia przez invoice.magico).
4. Architektura Funkcjonalna Systemu (Struktura Modułowa)
Aplikacja logicznie dzieli się na 5 głównych obszarów funkcjonalnych, które stanowią podstawę do zaprojektowania interfejsu użytkownika (UI):
4.1. Dashboard (Pulpit Zarządczy)
Centrum dowodzenia menedżera operacyjnego. Zapewnia natychmiastowy wgląd w kondycję całego parku maszynowego/sieci czujników bez konieczności przeklikiwania się przez poszczególne obiekty.
KPI Statusów: Zagregowane liczniki urządzeń (Wszystkie, Online, Offline, W trakcie serwisu).
Sekcja Alertów: Lista obiektów, które przekroczyły zdefiniowane normy bezpieczeństwa (kolorystyka czerwona/pomarańczowa).
Geolokalizacja: Mapa z oznaczonymi punktami pomiarowymi (opcjonalnie, na podstawie koordynatów GPS wysyłanych przez minikomputery).
4.2. Moduł: Urządzenia (Infrastruktura i Integracje)
Miejsce zarządzania fizycznymi punktami pomiarowymi. Moduł opiera się na sztywnych definicjach typów urządzeń, pisanych przez deweloperów.
Katalog urządzeń: Przeglądanie, filtrowanie po lokalizacji i typie.
Kreator urządzeń (Provisioning): 1. Wybór predefiniowanego typu (np. Sensor-LPG-v2).
2. Dynamiczny formularz konfiguracyjny (system podpowiada pola unikalne dla typu: np. maksymalna pojemność w litrach, adres IP, częstotliwość raportowania).
3. Wygenerowanie bezpiecznego tokenu dostępowego API (Bearer Token) dla minikomputera brzegowego.
4.3. Moduł: Aktualny Stan (Live Monitor)
Narzędzie dedykowane dla operatorów i techników. Pozwala na podgląd zachowania wybranego obiektu w czasie rzeczywistym.
Dynamiczny Grid Interfejsu: Layout dopasowuje się do typu urządzenia. Jeśli urządzenie mierzy 2 parametry, wyświetlane są 2 kafelki/wskaźniki zegarowe (gauges). Jeśli mierzy 10 parametrów interfejs generuje adekwatną liczbę kontenerów.
Wykresy Live: Wizualizacja trendu z ostatnich minut/godzin z automatycznym odświeżaniem danych.
4.4. Moduł: Raporty i Historia (Analityka)
Narzędzie służące do retrospektywnej analizy danych i wyciągania wniosków biznesowych.
Agregacja danych: Filtrowanie według ram czasowych (dni, tygodnie, miesiące) oraz konkretnych parametrów.
Eksport danych: Generowanie zestawień do formatów analitycznych (CSV, Excel) w celu dalszej obróbki lub audytów.
Analiza trendów: Narzędzia wykresów liniowych umożliwiające nałożenie na siebie różnych metryk w celu znalezienia korelacji (np. wpływ temperatury otoczenia na ciśnienie w zbiorniku).
4.5. Moduł: Ustawienia (Administracja)
Konfiguracja zachowania systemu oraz progów bezpieczeństwa.
Zarządzanie alertami (Progi krytyczne): Definiowanie reguł biznesowych typu: „Jeśli parametr gas_level na dowolnym urządzeniu typu gas_tank spadnie poniżej 15%, wyślij powiadomienie”.
Zarządzanie uprawnieniami: Integracja z systemem uprawnień magico.pro (kto może tylko przeglądać wykresy, a kto ma prawo dodawać nowe urządzenia).
5. Model Operacyjny (Jak to działa w praktyce?)
Warstwa Fizyczna (Edge): Przy zbiorniku lub w kurniku znajduje się minikomputer (np. sterownik przemysłowy, Raspberry Pi, brama Teltonika). Odpytuje on lokalne czujniki fizyczne (poprzez Modbus, OneWire itp.).
Warstwa Transmisyjna (API): Minikomputer formatuje uzyskane dane do ujednoliconego formatu JSON i za pomocą zapytania HTTP POST (zabezpieczonego tokenem) wypycha je do chmury telemetry.magico.
Warstwa Przetwarzania (Core): Aplikacja identyfikuje urządzenie po tokenie, sprawdza jakie metryki zostały przesłane, zapisuje je w bazie danych serii czasowych (Time-Series format) i ewentualnie uruchamia procesy sprawdzania alertów.
Warstwa Prezentacji (UI): Użytkownik końcowy widzi przetworzone, czytelne dane na wykresach i widgetach w panelu HTML.
# Dokumentacja Funkcjonalna Modułów: telemetry.magico
Ten dokument zawiera szczegółowy opis wymagań, logiki biznesowej oraz struktury interfejsu (UI) dla poszczególnych modułów aplikacji. Służy jako bezpośredni brief do przygotowania makiety HTML (Bootstrap) w ramach środowiska magico.pro.
---
## 1. Moduł: Dashboard (Pulpit Zarządczy)
### 1.1. Cel biznesowy
Zapewnienie użytkownikowi (zarządcy, dyspozytorowi) błyskawicznej oceny stanu technicznego i operacyjnego całej infrastruktury urządzeń bez konieczności wchodzenia w szczegóły każdego z nich.
### 1.2. Struktura i Układ UI (Makieta HTML)
* **Górny pasek statystyk (KPI Cards):** Cztery małe, sąsiadujące kafelki (`.card`) w jednym rzędzie:
* **Wszystkie urządzenia:** Łączna liczba zarejestrowanych maszyn (kolor neutralny / podstawowy magico).
* **Online:** Urządzenia, od których odebrano dane w zdefiniowanym oknie czasowym (zielony badge/tekst, ikona sygnału).
* **Offline:** Urządzenia, które utraciły łączność (czerwony badge/tekst, ikona wykrzyknika).
* **Aktywne Alerty:** Liczba urządzeń, na których aktualnie przekroczone są parametry krytyczne (pomarańczowy/czerwony puls).
* **Główna sekcja (Układ dwukolumnowy - `row` z podziałem `col-md-8` i `col-md-4`):**
* **Kolumna Lewa (Szeroka): Tabela "Krytyczne stany i alerty".** Lista urządzeń wymagających natychmiastowej uwagi. Kolumny: Nazwa urządzenia, Typ, Lokalizacja, Parametr przekroczony (np. *Poziom gazu: 8%*), Czas wystąpienia, Akcja (Przejdź do Live Monitora).
* **Kolumna Prawa (Wąska): Widget "Ostatnie zdarzenia" (Activity Log).** Lista typu timeline (oś czasu) pokazująca ostatnie zmiany statusów (np. *10:15 - Urządzenie 'Zbiornik 3' przeszło w tryb OFFLINE*, *09:42 - Urządzenie 'Kurnik B' zarejestrowało spadek temperatury*).
### 1.3. Logika i Zachowanie (JS / Backend)
* **Definicja Offline:** Urządzenie zmienia status na Offline automatycznie, jeśli czas od ostatniego odczytu (`last_seen_at`) jest większy niż dwukrotność jego zadeklarowanego interwału wysyłki danych.
* **Interakcja:** Kliknięcie w dowolny wiersz na liście alertów przenosi użytkownika bezpośrednio do widoku "Aktualny stan" (Live Monitor) z wybranym już tym konkretnym urządzeniem.
---
## 2. Moduł: Urządzenia (Device Management & Provisioning)
### 2.1. Cel biznesowy
Zarządzanie bazą fizycznych urządzeń oraz procesem ich bezpiecznego uwierzytelniania w chmurze magico.pro.
### 2.2. Struktura i Układ UI (Makieta HTML)
Moduł składa się z dwóch głównych widoków: **Listy urządzeń** oraz **Kreatora dodawania**.
#### Widok: Lista Urządzeń (Tabela główna)
* **Filtry nad tabelą:** Wyszukiwarka tekstowa, dropdown z filtrem po "Typie urządzenia" (Wszystkie, Zbiornik Gazu, Kurnik itp.) oraz filtr statusu (Online/Offline).
* **Tabela danych:**
* Nazwa (np. *Zbiornik LPG #1 - Stalowa Wola, ul. Przemysłowa*).
* Typ urządzenia (Badge z ikoną dedykowaną dla typu, np. ikona płomienia dla gazu, ikona termometru dla kurnika).
* Ostatni kontakt (Względny format daty, np. *Przed 2 min*, *3 dni temu*).
* Status (`.badge-success` dla Online, `.badge-danger` dla Offline).
* Akcje: Przycisk "Podgląd Live" (ikona oka), "Edycja" (ikona ołówka).
#### Widok: Kreator dodawania (Step-by-Step Wizard)
Zrealizowany za pomocą zakładek (`nav-tabs` lub niestandardowy stepper komponentu Bootstrap).
* **Krok 1: Wybór typu urządzenia.** Kafelki z ikonami i opisami predefiniowanych integracji (np. `Karta 1: Zbiornik Gazu standard (Modbus/LPG)`, `Karta 2: Sterownik Mikroklimatu Kurnika v1`). Użytkownik klika jeden z nich i przechodzi dalej.
* **Krok 2: Konfiguracja parametrów (Dynamiczny formularz).** Pola ładują się w zależności od wyboru w Kroku 1:
* *Dla Zbiornika Gazu:* Pole tekstowe: Pojemność nominalna (litry), Maksymalne ciśnienie bezpieczne (bar), Stały adres IP minikomputera (opcjonalnie).
* *Dla Kurnika:* Pole liczbowe: Liczba stref grzewczych, Liczba wentylatorów, Interwał próbkowania (w sekundach).
* **Krok 3: Generowanie tokenu.** Ekran podsumowania, na którym system wyświetla wygenerowany unikalny klucz API (`Device API Token`). Zawiera przycisk "Kopiuj do schowka".
### 2.3. Logika i Zachowanie (JS / Backend)
* **Bezpieczeństwo tokenów:** Token API (`api_token`) jest widoczny w pełnej krasie tylko raz podczas tworzenia urządzenia (Krok 3). Potem w bazie jest haszowany lub maskowany (np. `mag_tlm_•••••••••1a2b`).
* **Dynamiczne renderowanie:** W makiety HTML warto wbudować mechanizm (np. proste przełączanie klasami `d-none`), który zasymuluje, jak zmienia się formularz w Kroku 2 w zależności od wybranego w Kroku 1 typu urządzenia.
---
## 3. Moduł: Aktualny Stan (Live Monitor)
### 3.1. Cel biznesowy
Umożliwienie technikowi lub operatorowi zdalnego podglądu parametrów pracy wybranego obiektu w trybie "tu i teraz" w celu weryfikacji awarii lub kontroli procesów.
### 3.2. Struktura i Układ UI (Makieta HTML)
* **Górny pasek kontekstowy:** Duży Dropdown (`<select>` z obsługą wyszukiwania, np. Select2 w Bootstrapie) do wyboru urządzenia. Obok dropdownu wyświetla się duży badge aktualnego statusu wybranego urządzenia (Online/Offline) oraz data i godzina ostatniego odebranego pakietu danych.
* **Siatka wskaźników (Dynamic Grid):** Rząd kafelków (`row-cols-1 row-cols-md-3 g-4`), gdzie każdy kafelek prezentuje **jedną metrykę/kanał danych**.
* *Przykład komponentu wskaźnika:* Duża wartość numeryczna w centrum (np. **22.4 °C** lub **74.5 %**), pod nią nazwa metryki ("Temperatura strefa A", "Poziom napełnienia"), a na dole mały badge informujący o trendzie w ciągu ostatniej godziny (np. zielona strzałka w górę "Wzrost o 1.2%").
* **Dolna sekcja: Mini-wykres Live.** Wykres liniowy (zajmujący pełną szerokość `col-12`), pokazujący dane z wybranej kluczowej metryki z ostatnich 2 godzin, odświeżany automatycznie bez przeładowania strony.
### 3.3. Logika i Zachowanie (JS / Backend)
* **Agnostyczność interfejsu:** Layout HTML musi być przygotowany na to, że kafelków wskaźników może być 2 (dla prostego zbiornika), a może być 12 (dla rozbudowanego kurnika). Kafelki powinny płynnie zawijać się do kolejnych rzędów dzięki klasom flexbox Bootstrapa.
* **Odświeżanie danych:** Widok symuluje połączenie WebSocket lub regularny odpytywacz (Polling AJAX) co 10-30 sekund, który aktualizuje wartości liczbowe w kafelkach oraz dopisuje nowy punkt na mini-wykresie.
---
## 4. Moduł: Raporty i Historia (Analityka Historyczna)
### 4.1. Cel biznesowy
Dostarczanie twardych danych analitycznych do celów optymalizacyjnych (planowanie dostaw gazu, audyty warunków chowu drobiu) oraz eksport danych do zewnętrznych systemów lub arkuszy kalkulacyjnych.
### 4.2. Struktura i Układ UI (Makieta HTML)
* **Pasek filtrów (Górny horyzontalny formularz card):**
* Wybór urządzenia (Dropdown).
* Wybór metryk (Multiselect checkbox / dropdown np. użytkownik chce zobaczyć tylko poziom gazu, ignorując napięcie baterii minikomputera).
* Zakres dat (Komponent Date-Range Picker: Od, Do, wraz z szybkimi filtrami: *Dzisiaj, Ostatnie 7 dni, Ten miesiąc*).
* Przycisk akcji: "Generuj raport" (niebieski) oraz "Eksportuj do CSV" (zielony).
* **Sekcja Główna - Wykres Analityczny:** Duży kontener wykresu liniowego (np. przy użyciu biblioteki Chart.js wbudowanej w layout Bootstrap). Wykres musi obsługiwać wiele osi Y, jeśli użytkownik porównuje różne jednostki (np. lewa oś dla temperatury w `°C`, prawa dla wilgotności w `%`).
* **Sekcja Dolna - Tabela Surowych Danych:** Tabela z paginacją (np. stylizowana pod DataTables). Kolumny: Data i godzina (Timestamp), Nazwa metryki, Wartość, Jednostka.
### 4.3. Logika i Zachowanie (JS / Backend)
* **Agregacja danych (Downsampling):** Ponieważ dane telemetryczne przyrastają w ogromnym tempie, backend przy raportach z długich okresów (np. 3 miesiące) nie może przesłać milionów rekordów do JS. System musi agregować dane w locie (np. średnia godzinowa lub średnia dobowa) w zależności od wybranego zakresu czasu.
---
## 5. Moduł: Ustawienia (Settings & Alert Rules)
### 5.1. Cel biznesowy
Konfiguracja zachowań automatycznych systemu, w tym definiowanie progów bezpieczeństwa dla zbieranych danych.
### 5.2. Struktura i Układ UI (Makieta HTML)
Zorganizowany w formie pionowych zakładek (List-group tabs po lewej stronie, zawartość po prawej).
#### Zakładka 1: Reguły Alertów (Alerts Configuration)
* **Lista aktywnych reguł:** Tabela pokazująca zdefiniowane progi.
* **Przycisk "Dodaj nową regułę"**, który otwiera modal (okno pop-up):
* Dropdown: "Jeśli urządzenie typu..." (Wybór typu, np. *Zbiornik Gazu*).
* Dropdown: "...zarejestruje parametr..." (Dynamiczna lista metryk dla typu, np. *gas_level*).
* Dropdown warunku: (Mniejsze niż, Większe niż, Równe).
* Pole numeryczne: Wartość progu (np. *15*).
* Sekcja akcji: Checkboxy "Wyślij e-mail do administratora", "Załóż ticket w helpdesk.magico".
#### Zakładka 2: Globalna Konfiguracja Telemetrii
* Formularze konfiguracyjne:
* Czas retencji danych (Dropdown: *Przechowuj surowe dane przez: 3 miesiące / 6 miesięcy / rok*).
* Domyślny interwał sprawdzania statusów offline (np. *Co ile minut system ma weryfikować brak kontaktu z minikomputerami*).
### 5.3. Logika i Zachowanie (JS / Backend)
* **Integracja wewnątrzmagico:** Powiązanie akcji alertu z modułem `helpdesk.magico` musi opierać się na wewnętrznym API ekosystemu magico.pro. W makiecie należy przewidzieć elementy UI (np. selecty grup użytkowników, kategorie ticketów), które odzwierciedlają tę integrację.
+174
View File
@@ -0,0 +1,174 @@
<?php
$enablePrototypeComments = true;
include '../../header-telemetria.php';
?>
<div class="container-xxl flex-grow-1 container-p-y">
<div class="d-flex justify-content-between align-items-center py-3 mb-4">
<h4 class="fw-bold mb-0">
Telemetria <span class="text-muted fw-light">/ Urządzenia (Sprzęt)</span>
</h4>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addDeviceModal">
<i class="bx bx-plus me-1"></i> Dodaj urządzenie
</button>
</div>
<!-- Tabela Urządzeń -->
<div class="card">
<div class="table-responsive text-nowrap">
<table class="table table-hover">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Adres MAC</th>
<th>Nazwa własna</th>
<th>Status</th>
<th>Ostatnia aktywność</th>
<th>Akcje</th>
</tr>
</thead>
<tbody class="table-border-bottom-0">
<tr>
<td><strong>1024</strong></td>
<td class="text-monospace">00:1B:44:11:3A:B7</td>
<td>Nadajnik Główny #1</td>
<td><span class="badge bg-label-success"><i class="bx bx-wifi me-1"></i> Online</span></td>
<td>Dzisiaj, 14:52:10</td>
<td>
<button type="button" class="btn btn-icon btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#editDeviceModal" title="Edytuj">
<i class="bx bx-pencil"></i>
</button>
<button type="button" class="btn btn-icon btn-outline-danger btn-sm" title="Usuń">
<i class="bx bx-trash"></i>
</button>
</td>
</tr>
<tr>
<td><strong>1025</strong></td>
<td class="text-monospace">00:1B:44:11:4F:C1</td>
<td>Moduł zapasowy 1</td>
<td><span class="badge bg-label-success"><i class="bx bx-wifi me-1"></i> Online</span></td>
<td>Dzisiaj, 14:50:05</td>
<td>
<button type="button" class="btn btn-icon btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#editDeviceModal" title="Edytuj">
<i class="bx bx-pencil"></i>
</button>
<button type="button" class="btn btn-icon btn-outline-danger btn-sm" title="Usuń">
<i class="bx bx-trash"></i>
</button>
</td>
</tr>
<tr>
<td><strong>1026</strong></td>
<td class="text-monospace">00:1B:44:22:9C:E2</td>
<td>Sterownik Klimatu Alpha</td>
<td><span class="badge bg-label-danger"><i class="bx bx-wifi-off me-1"></i> Offline</span></td>
<td>Wczoraj, 18:22:00</td>
<td>
<button type="button" class="btn btn-icon btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#editDeviceModal" title="Edytuj">
<i class="bx bx-pencil"></i>
</button>
<button type="button" class="btn btn-icon btn-outline-danger btn-sm" title="Usuń">
<i class="bx bx-trash"></i>
</button>
</td>
</tr>
<tr>
<td><strong>1027</strong></td>
<td class="text-monospace">00:1B:44:55:00:AA</td>
<td>Nowy czujnik testowy</td>
<td><span class="badge bg-label-secondary"><i class="bx bx-time me-1"></i> Brak danych</span></td>
<td>-</td>
<td>
<button type="button" class="btn btn-icon btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#editDeviceModal" title="Edytuj">
<i class="bx bx-pencil"></i>
</button>
<button type="button" class="btn btn-icon btn-outline-danger btn-sm" title="Usuń">
<i class="bx bx-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Modal Dodawania Urządzenia -->
<div class="modal fade" id="addDeviceModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Rejestracja nowego urządzenia</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="alert alert-primary" role="alert">
<i class="bx bx-info-circle me-1"></i> Numer ID zostanie wygenerowany automatycznie po zapisaniu.
</div>
<div class="mb-3">
<label class="form-label">Adres MAC sprzętu <span class="text-danger">*</span></label>
<input type="text" class="form-control text-uppercase" placeholder="np. 00:1B:44:11:3A:B7">
</div>
<div class="mb-3">
<label class="form-label">Nazwa własna</label>
<input type="text" class="form-control" placeholder="np. Nadajnik Północ">
<div class="form-text">Przyjazna nazwa ułatwiająca identyfikację.</div>
</div>
<div class="mb-3">
<label class="form-label">Notatki</label>
<textarea class="form-control" rows="3" placeholder="Dodatkowe informacje serwisowe, uwagi..."></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Anuluj</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Zapisz urządzenie</button>
</div>
</div>
</div>
</div>
<!-- Modal Edycji Urządzenia -->
<div class="modal fade" id="editDeviceModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edycja urządzenia</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">ID</label>
<input type="text" class="form-control" value="1024" disabled>
</div>
<div class="mb-3">
<label class="form-label">Adres MAC sprzętu <span class="text-danger">*</span></label>
<input type="text" class="form-control text-uppercase" value="00:1B:44:11:3A:B7" disabled>
<div class="form-text">Adresu fizycznego nie można zmienić. Skontaktuj się ze wsparciem.</div>
</div>
<div class="mb-3">
<label class="form-label">Nazwa własna</label>
<input type="text" class="form-control" value="Nadajnik Główny #1">
</div>
<div class="mb-3">
<label class="form-label">Notatki</label>
<textarea class="form-control" rows="3" placeholder="Dodatkowe informacje serwisowe, uwagi...">Wymieniona bateria 10.02.2025.</textarea>
</div>
</div>
<div class="modal-footer d-flex justify-content-between">
<button type="button" class="btn btn-outline-danger">Resetuj Token</button>
<div>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Anuluj</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Zapisz zmiany</button>
</div>
</div>
</div>
</div>
</div>
<div class="content-backdrop fade"></div>
<?php include '../../footer.php'; ?>
</body>
</html>
+326
View File
@@ -0,0 +1,326 @@
<?php
$enablePrototypeComments = true;
include '../../header-telemetria.php';
?>
<div class="container-xxl flex-grow-1 container-p-y">
<div class="d-flex justify-content-between align-items-center py-3 mb-4">
<h4 class="fw-bold mb-0">
Telemetria <span class="text-muted fw-light">/ Dashboard główny</span>
</h4>
<div class="d-flex align-items-center">
<button type="button" class="btn btn-primary me-2">
<i class="bx bx-plus me-1"></i> Nowy dashboard
</button>
<button type="button" class="btn btn-outline-secondary me-2" id="toggleDashboardEditBtn" onclick="toggleDashboardEditMode()">
<i class="bx bx-customize me-1"></i> Tryb edycji
</button>
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#shareModal">
<i class="bx bx-share-alt me-1"></i> Udostępnij
</button>
</div>
</div>
<div class="row g-4">
<!-- Instalacja 1: Prawidłowa -->
<div class="col-xl-4 col-lg-6 col-md-6">
<div
class="card h-100 shadow-sm border-0 border-top border-warning border-3 hover-border-primary transition-all position-relative">
<div class="edit-mode-overlay d-none position-absolute top-0 start-0 w-100 h-100 bg-white bg-opacity-75 zindex-2 d-flex flex-column justify-content-center align-items-center border border-2 border-dashed border-primary rounded" style="cursor: move;">
<i class="bx bx-move fs-1 text-primary mb-2"></i>
<button type="button" class="btn btn-sm btn-danger" onclick="this.closest('.col-xl-4').remove()"><i class="bx bx-trash"></i> Usuń z widoku</button>
</div>
<div class="card-header d-flex justify-content-between align-items-start border-bottom pb-3">
<div>
<h6 class="mb-1 fw-bold text-dark">Zbiornik LPG #1</h6>
<small class="text-muted"><i class="bx bx-map"></i> Stalowa Wola, ul. Przemysłowa 5</small>
</div>
<span class="badge bg-success"><i class="bx bx-wifi"></i> ONLINE</span>
</div>
<div class="card-body pt-4 d-flex">
<div class="me-4">
<div class="tank-container position-relative" style="width: 70px; height: 160px;">
<svg viewBox="0 0 100 300" width="100%" height="100%">
<path d="M 35 260 L 30 295 M 65 260 L 70 295" stroke="#a1acb8" stroke-width="6"
stroke-linecap="round" />
<line x1="20" y1="295" x2="80" y2="295" stroke="#a1acb8" stroke-width="4"
stroke-linecap="round" />
<path d="M 20 40 A 30 20 0 0 1 80 40 L 80 250 A 30 20 0 0 1 20 250 Z" fill="#f8f9fa"
stroke="#d9dee3" stroke-width="4" />
<clipPath id="fillClip1">
<path d="M 22 42 A 28 18 0 0 1 78 42 L 78 248 A 28 18 0 0 1 22 248 Z" />
</clipPath>
<defs>
<linearGradient id="gasGradient1" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#ffab00" />
<stop offset="100%" stop-color="#e69a00" />
</linearGradient>
</defs>
<g clip-path="url(#fillClip1)">
<rect x="0" y="120" width="100" height="250" fill="url(#gasGradient1)" />
</g>
<path d="M 20 90 A 30 10 0 0 0 80 90" stroke="#d9dee3" stroke-width="2" fill="none" />
<path d="M 20 150 A 30 10 0 0 0 80 150" stroke="#d9dee3" stroke-width="2" fill="none" />
<path d="M 20 210 A 30 10 0 0 0 80 210" stroke="#d9dee3" stroke-width="2" fill="none" />
</svg>
<div class="position-absolute top-50 start-50 translate-middle text-center"
style="margin-top: -10px;">
<h5 class="mb-0 fw-bolder text-dark"
style="text-shadow: 0px 0px 5px rgba(255,255,255,0.8);">60%</h5>
</div>
</div>
</div>
<div class="flex-grow-1 d-flex flex-column justify-content-center">
<div class="mb-3">
<span class="text-muted d-block small text-uppercase fw-bold mb-1">Objętość</span>
<h4 class="mb-0 text-dark">1 620 <small class="text-muted fs-6 fw-normal">l</small></h4>
</div>
<div class="mb-3">
<span class="text-muted d-block small text-uppercase fw-bold mb-1">Ciśnienie</span>
<h5 class="mb-0 text-dark">5.24 <small class="text-muted fs-6 fw-normal">bar</small></h5>
</div>
<div>
<span class="text-muted d-block small text-uppercase fw-bold mb-1">Bateria</span>
<h6 class="mb-0 text-dark"><i class='bx bx-battery text-success me-1'></i> 12.4 V</h6>
</div>
</div>
</div>
<div class="card-footer bg-transparent border-top">
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted"><i class="bx bx-time-five"></i> Odczyt: 2 min temu</small>
<a href="installation-live.php" class="btn btn-sm btn-primary">Szczegóły</a>
</div>
</div>
</div>
</div>
<!-- Instalacja 2: Alarm Niski Poziom -->
<div class="col-xl-4 col-lg-6 col-md-6">
<div class="card h-100 shadow-sm border-0 border-top border-danger border-3 position-relative">
<div class="edit-mode-overlay d-none position-absolute top-0 start-0 w-100 h-100 bg-white bg-opacity-75 zindex-2 d-flex flex-column justify-content-center align-items-center border border-2 border-dashed border-primary rounded" style="cursor: move;">
<i class="bx bx-move fs-1 text-primary mb-2"></i>
<button type="button" class="btn btn-sm btn-danger" onclick="this.closest('.col-xl-4').remove()"><i class="bx bx-trash"></i> Usuń z widoku</button>
</div>
<div class="card-header d-flex justify-content-between align-items-start border-bottom pb-3">
<div>
<h6 class="mb-1 fw-bold text-dark">Zbiornik LPG #2</h6>
<small class="text-muted"><i class="bx bx-map"></i> Rzeszów, ul. Podkarpacka 12</small>
</div>
<span class="badge bg-danger"><i class="bx bx-error-circle"></i> ALARM</span>
</div>
<div class="card-body pt-4 d-flex">
<div class="me-4">
<div class="tank-container position-relative" style="width: 70px; height: 160px;">
<svg viewBox="0 0 100 300" width="100%" height="100%">
<path d="M 35 260 L 30 295 M 65 260 L 70 295" stroke="#a1acb8" stroke-width="6"
stroke-linecap="round" />
<line x1="20" y1="295" x2="80" y2="295" stroke="#a1acb8" stroke-width="4"
stroke-linecap="round" />
<path d="M 20 40 A 30 20 0 0 1 80 40 L 80 250 A 30 20 0 0 1 20 250 Z" fill="#f8f9fa"
stroke="#d9dee3" stroke-width="4" />
<clipPath id="fillClip2">
<path d="M 22 42 A 28 18 0 0 1 78 42 L 78 248 A 28 18 0 0 1 22 248 Z" />
</clipPath>
<defs>
<linearGradient id="gasGradient2" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#ff3e1d" />
<stop offset="100%" stop-color="#cc3217" />
</linearGradient>
</defs>
<g clip-path="url(#fillClip2)">
<rect x="0" y="215" width="100" height="250" fill="url(#gasGradient2)" />
</g>
<path d="M 20 90 A 30 10 0 0 0 80 90" stroke="#d9dee3" stroke-width="2" fill="none" />
<path d="M 20 150 A 30 10 0 0 0 80 150" stroke="#d9dee3" stroke-width="2" fill="none" />
<path d="M 20 210 A 30 10 0 0 0 80 210" stroke="#d9dee3" stroke-width="2" fill="none" />
</svg>
<div class="position-absolute top-50 start-50 translate-middle text-center"
style="margin-top: 30px;">
<h5 class="mb-0 fw-bolder text-danger"
style="text-shadow: 0px 0px 5px rgba(255,255,255,0.8);">12%</h5>
</div>
</div>
</div>
<div class="flex-grow-1 d-flex flex-column justify-content-center">
<div class="mb-3">
<span class="text-muted d-block small text-uppercase fw-bold mb-1">Objętość</span>
<h4 class="mb-0 text-danger fw-bold">324 <small class="text-muted fs-6 fw-normal">l</small>
</h4>
</div>
<div class="mb-3">
<span class="text-muted d-block small text-uppercase fw-bold mb-1">Ciśnienie</span>
<h5 class="mb-0 text-dark">3.10 <small class="text-muted fs-6 fw-normal">bar</small></h5>
</div>
<div>
<span class="text-muted d-block small text-uppercase fw-bold mb-1">Bateria</span>
<h6 class="mb-0 text-dark"><i class='bx bx-battery text-success me-1'></i> 12.3 V</h6>
</div>
</div>
</div>
<div class="card-footer bg-transparent border-top">
<div class="d-flex justify-content-between align-items-center">
<small class="text-danger fw-bold"><i class="bx bx-error"></i> Niski poziom gazu!</small>
<a href="installation-live.php" class="btn btn-sm btn-outline-danger">Zarządzaj</a>
</div>
</div>
</div>
</div>
<!-- Instalacja 3: Offline -->
<div class="col-xl-4 col-lg-6 col-md-6">
<div class="card h-100 shadow-sm border-0 border-top border-secondary border-3 position-relative">
<div class="edit-mode-overlay d-none position-absolute top-0 start-0 w-100 h-100 bg-white bg-opacity-75 zindex-2 d-flex flex-column justify-content-center align-items-center border border-2 border-dashed border-primary rounded" style="cursor: move;">
<i class="bx bx-move fs-1 text-primary mb-2"></i>
<button type="button" class="btn btn-sm btn-danger" onclick="this.closest('.col-xl-4').remove()"><i class="bx bx-trash"></i> Usuń z widoku</button>
</div>
<div class="card-header d-flex justify-content-between align-items-start border-bottom pb-3">
<div>
<h6 class="mb-1 fw-bold text-dark">Zbiornik LPG #3</h6>
<small class="text-muted"><i class="bx bx-map"></i> Lublin, Al. Kraśnicka 100</small>
</div>
<span class="badge bg-secondary"><i class="bx bx-wifi-off"></i> OFFLINE</span>
</div>
<div class="card-body pt-4 d-flex">
<div class="me-4" style="opacity: 0.5; filter: grayscale(100%);">
<div class="tank-container position-relative" style="width: 70px; height: 160px;">
<svg viewBox="0 0 100 300" width="100%" height="100%">
<path d="M 35 260 L 30 295 M 65 260 L 70 295" stroke="#a1acb8" stroke-width="6"
stroke-linecap="round" />
<line x1="20" y1="295" x2="80" y2="295" stroke="#a1acb8" stroke-width="4"
stroke-linecap="round" />
<path d="M 20 40 A 30 20 0 0 1 80 40 L 80 250 A 30 20 0 0 1 20 250 Z" fill="#f8f9fa"
stroke="#d9dee3" stroke-width="4" />
<clipPath id="fillClip3">
<path d="M 22 42 A 28 18 0 0 1 78 42 L 78 248 A 28 18 0 0 1 22 248 Z" />
</clipPath>
<defs>
<linearGradient id="gasGradient3" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#8592a3" />
<stop offset="100%" stop-color="#697a8d" />
</linearGradient>
</defs>
<g clip-path="url(#fillClip3)">
<rect x="0" y="60" width="100" height="250" fill="url(#gasGradient3)" />
</g>
<path d="M 20 90 A 30 10 0 0 0 80 90" stroke="#d9dee3" stroke-width="2" fill="none" />
<path d="M 20 150 A 30 10 0 0 0 80 150" stroke="#d9dee3" stroke-width="2" fill="none" />
<path d="M 20 210 A 30 10 0 0 0 80 210" stroke="#d9dee3" stroke-width="2" fill="none" />
</svg>
<div class="position-absolute top-50 start-50 translate-middle text-center"
style="margin-top: -30px;">
<h5 class="mb-0 fw-bolder text-dark"
style="text-shadow: 0px 0px 5px rgba(255,255,255,0.8);">85%</h5>
</div>
</div>
</div>
<div class="flex-grow-1 d-flex flex-column justify-content-center">
<div class="mb-3">
<span class="text-muted d-block small text-uppercase fw-bold mb-1">Objętość
(Ostatnia)</span>
<h4 class="mb-0 text-muted">2 295 <small class="text-muted fs-6 fw-normal">l</small></h4>
</div>
<div class="mb-3">
<span class="text-muted d-block small text-uppercase fw-bold mb-1">Ciśnienie
(Ostatnie)</span>
<h5 class="mb-0 text-muted">6.12 <small class="text-muted fs-6 fw-normal">bar</small></h5>
</div>
<div>
<span class="text-muted d-block small text-uppercase fw-bold mb-1">Bateria</span>
<h6 class="mb-0 text-muted"><i class='bx bx-battery text-danger me-1'></i> 10.5 V (Low)</h6>
</div>
</div>
</div>
<div class="card-footer bg-transparent border-top">
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted"><i class="bx bx-time-five"></i> Odczyt: Wczoraj, 18:40</small>
<a href="installation-live.php" class="btn btn-sm btn-outline-secondary">Szczegóły</a>
</div>
</div>
</div>
</div>
<!-- Dodaj kartę (Edit mode only) -->
<div class="col-xl-4 col-lg-6 col-md-6 edit-mode-item d-none">
<div class="card h-100 shadow-sm border-2 border-dashed border-primary bg-transparent d-flex justify-content-center align-items-center cursor-pointer hover-border-primary transition-all" style="min-height: 250px;">
<div class="text-center text-primary">
<i class="bx bx-plus-circle fs-1 mb-2"></i>
<h6 class="text-primary mb-0">Dodaj instalację do dashboardu</h6>
</div>
</div>
</div>
</div>
</div>
<script>
function toggleDashboardEditMode() {
document.body.classList.toggle('dashboard-edit-mode');
const isEdit = document.body.classList.contains('dashboard-edit-mode');
document.querySelectorAll('.edit-mode-overlay, .edit-mode-item').forEach(el => {
if(isEdit) {
el.classList.remove('d-none');
} else {
el.classList.add('d-none');
}
});
const toggleBtn = document.getElementById('toggleDashboardEditBtn');
if(isEdit) {
toggleBtn.classList.replace('btn-outline-secondary', 'btn-success');
toggleBtn.innerHTML = '<i class="bx bx-check me-1"></i> Zakończ edycję';
} else {
toggleBtn.classList.replace('btn-success', 'btn-outline-secondary');
toggleBtn.innerHTML = '<i class="bx bx-customize me-1"></i> Tryb edycji';
}
}
function toggleLinkVisibility(checkbox) {
const linkContainer = document.getElementById('shareLinkContainer');
if (checkbox.checked) {
linkContainer.classList.remove('d-none');
} else {
linkContainer.classList.add('d-none');
}
}
</script>
<!-- Modal Udostępniania -->
<div class="modal fade" id="shareModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Udostępnianie Dashboardu</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Widoczność</label>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="publicDashboardSwitch" onchange="toggleLinkVisibility(this)">
<label class="form-check-label" for="publicDashboardSwitch">Dashboard publiczny (dostęp przez link)</label>
</div>
</div>
<div class="mb-3 d-none" id="shareLinkContainer">
<label class="form-label">Link do udostępnienia</label>
<div class="input-group input-group-merge">
<input type="text" class="form-control" value="https://telemetria.magico/d/xyz123" readonly>
<span class="input-group-text cursor-pointer" onclick="alert('Skopiowano link!')"><i class="bx bx-copy"></i></span>
</div>
<small class="text-muted mt-1 d-block">Osoby posiadające ten link będą mogły zobaczyć dashboard tylko do odczytu.</small>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Zamknij</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Zapisz</button>
</div>
</div>
</div>
</div>
<div class="content-backdrop fade"></div>
<?php include '../../footer.php'; ?>
</body>
</html>
+581
View File
@@ -0,0 +1,581 @@
<?php
$enablePrototypeComments = true;
include '../../header-telemetria.php';
?>
<div class="container-xxl flex-grow-1 container-p-y">
<h4 class="fw-bold py-3 mb-4">
Telemetria <span class="text-muted fw-light">/ Dodaj instalację</span>
</h4>
<div class="card">
<div class="card-body">
<!-- Stepper / Wizard nawigacja -->
<div class="d-flex justify-content-between mb-4 border-bottom pb-3">
<div class="text-center w-100 wizard-step active-step" id="wizard-tab-1">
<div class="badge bg-primary rounded-pill mb-2 p-2"><i class="bx bx-category fs-4"></i>
</div>
<h6 class="mb-0 text-primary fw-bold">1. Typ instalacji</h6>
</div>
<div class="text-center w-100 wizard-step opacity-50" id="wizard-tab-2">
<div class="badge bg-secondary rounded-pill mb-2 p-2"><i
class="bx bx-slider-alt fs-4"></i></div>
<h6 class="mb-0 text-muted">2. Konfiguracja</h6>
</div>
<div class="text-center w-100 wizard-step opacity-50" id="wizard-tab-3">
<div class="badge bg-secondary rounded-pill mb-2 p-2"><i class="bx bx-check-shield fs-4"></i>
</div>
<h6 class="mb-0 text-muted">3. Zakończenie</h6>
</div>
</div>
<!-- KROK 1 -->
<div id="wizard-step-1" class="wizard-content">
<h5 class="mb-4 text-center">Wybierz typ integracji dla nowej instalacji</h5>
<div class="row g-4 justify-content-center">
<div class="col-md-5">
<div class="card border border-2 border-primary shadow-sm h-100 cursor-pointer"
onclick="goToStep2('gas')">
<div class="card-body text-center">
<div
class="avatar avatar-xl bg-label-warning mx-auto mb-3 rounded-circle d-flex align-items-center justify-content-center">
<i class="bx bxs-flame fs-1"></i>
</div>
<h5 class="card-title mb-2">Zbiornik Gazu (LPG/CNG)</h5>
<p class="text-muted small">Standardowy czujnik Modbus dla zbiorników paliw
i gazu z pomiarem poziomu napełnienia i ciśnienia.</p>
</div>
</div>
</div>
<div class="col-md-5">
<div class="card border border-2 border-transparent hover-border-primary shadow-sm h-100 cursor-pointer"
onclick="goToStep2('farm')">
<div class="card-body text-center">
<div
class="avatar avatar-xl bg-label-info mx-auto mb-3 rounded-circle d-flex align-items-center justify-content-center">
<i class="bx bxs-thermometer fs-1"></i>
</div>
<h5 class="card-title mb-2">Sterownik Mikroklimatu (Kurnik)</h5>
<p class="text-muted small">Integracja z kontrolerami wentylacji i
ogrzewania. Obsługa wielu stref temperaturowych i CO2.</p>
</div>
</div>
</div>
</div>
</div>
<!-- KROK 2 -->
<div id="wizard-step-2" class="wizard-content d-none">
<h5 class="mb-4">Konfiguracja specyficzna dla instalacji</h5>
<!-- Formularz wspólny -->
<div class="row mb-3">
<div class="col-md-6 mb-3">
<label class="form-label">Nazwa instalacji (Własna)</label>
<input type="text" class="form-control" placeholder="np. Zbiornik Główny Północ">
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Lokalizacja / Adres</label>
<input type="text" class="form-control" placeholder="np. ul. Fabryczna 1, Warszawa">
</div>
</div>
<hr>
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h6 class="text-muted mb-1">Przypięte Urządzenia (Hardware)</h6>
<p class="small text-muted mb-0">Wybierz fizyczne urządzenia z magazynu i przypnij je do tej instalacji.</p>
</div>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#assignDeviceModal">
<i class="bx bx-link"></i> Przypisz urządzenie
</button>
</div>
<!-- Parametry Zbiornik (pokazywane dynamicznie) -->
<div id="form-gas" class="dynamic-form-part">
<div class="card bg-lighter border mb-4">
<div class="card-header border-bottom d-flex justify-content-between align-items-center py-3">
<div class="d-flex align-items-center">
<div class="avatar avatar-sm bg-white border me-2 d-flex justify-content-center align-items-center">
<i class="bx bx-chip text-primary"></i>
</div>
<h6 class="mb-0">Nadajnik Główny #1 <span class="text-muted fw-normal ms-1">(ID: 1024, MAC: 00:1B:44:11:3A:B7)</span></h6>
</div>
<button class="btn btn-sm btn-outline-danger"><i class="bx bx-unlink"></i> Odłącz</button>
</div>
<div class="card-body pt-3 pb-0">
<p class="small text-muted mb-2">Parametry obsługiwane przez to urządzenie:</p>
<div class="table-responsive text-nowrap mb-2">
<table class="table table-sm table-bordered bg-white">
<thead class="table-light">
<tr>
<th>Klucz</th>
<th>Nazwa</th>
<th>Ostatnia wartość</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="text-monospace text-muted">gas_level</span></td>
<td>Poziom napełnienia</td>
<td><span class="badge bg-label-secondary">Brak danych</span></td>
</tr>
<tr>
<td><span class="text-monospace text-muted">gas_pressure</span></td>
<td>Ciśnienie</td>
<td><span class="badge bg-label-secondary">Brak danych</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card bg-lighter border mb-4">
<div class="card-header border-bottom d-flex justify-content-between align-items-center py-3">
<div class="d-flex align-items-center">
<div class="avatar avatar-sm bg-white border me-2 d-flex justify-content-center align-items-center">
<i class="bx bx-chip text-primary"></i>
</div>
<h6 class="mb-0">Nadajnik Zapasowy #2 <span class="text-muted fw-normal ms-1">(ID: 1025, MAC: 00:1B:44:11:4F:C1)</span></h6>
</div>
<button class="btn btn-sm btn-outline-danger"><i class="bx bx-unlink"></i> Odłącz</button>
</div>
<div class="card-body pt-3 pb-0">
<p class="small text-muted mb-2">Parametry obsługiwane przez to urządzenie:</p>
<div class="table-responsive text-nowrap mb-2">
<table class="table table-sm table-bordered bg-white">
<thead class="table-light">
<tr>
<th>Klucz</th>
<th>Nazwa</th>
<th>Ostatnia wartość</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="text-monospace text-muted">gps_lat</span></td>
<td>Szerokość geogr. (GPS)</td>
<td><span class="badge bg-label-secondary">Brak sygnału</span></td>
</tr>
<tr>
<td><span class="text-monospace text-muted">gps_lon</span></td>
<td>Długość geogr. (GPS)</td>
<td><span class="badge bg-label-secondary">Brak sygnału</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Parametry Kurnik (pokazywane dynamicznie) -->
<div id="form-farm" class="dynamic-form-part d-none">
<div class="card bg-lighter border mb-4">
<div class="card-header border-bottom d-flex justify-content-between align-items-center py-3">
<div class="d-flex align-items-center">
<div class="avatar avatar-sm bg-white border me-2 d-flex justify-content-center align-items-center">
<i class="bx bx-chip text-primary"></i>
</div>
<h6 class="mb-0">Sterownik Klimatu Alpha <span class="text-muted fw-normal ms-1">(ID: 1026, MAC: 00:1B:44:22:9C:E2)</span></h6>
</div>
<button class="btn btn-sm btn-outline-danger"><i class="bx bx-unlink"></i> Odłącz</button>
</div>
<div class="card-body pt-3 pb-0">
<p class="small text-muted mb-2">Parametry obsługiwane przez to urządzenie:</p>
<div class="table-responsive text-nowrap mb-2">
<table class="table table-sm table-bordered bg-white">
<thead class="table-light">
<tr>
<th>Klucz</th>
<th>Nazwa</th>
<th>Ostatnia wartość</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="text-monospace text-muted">temp_zone_1</span></td>
<td>Temperatura (Strefa 1)</td>
<td><span class="badge bg-label-secondary">Brak danych</span></td>
</tr>
<tr>
<td><span class="text-monospace text-muted">vent_status</span></td>
<td>Status wentylacji</td>
<td><span class="badge bg-label-secondary">Brak danych</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<hr class="my-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="text-muted mb-0">Konfigurator powiadomień (Alerty)</h6>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="addAlertBlock()">
<i class="bx bx-plus"></i> Dodaj alert
</button>
</div>
<div id="alerts-container">
<!-- Pojedynczy blok alertu -->
<div class="card bg-lighter border mb-4 alert-block">
<div class="card-header border-bottom d-flex justify-content-between align-items-center py-3">
<h6 class="mb-0 text-danger"><i class="bx bx-bell"></i> Nowy Alert</h6>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.alert-block').remove()"><i class="bx bx-trash"></i> Usuń ten alert</button>
</div>
<div class="card-body pt-3 pb-2">
<div class="d-flex align-items-center mb-3">
<p class="small text-muted fw-bold mb-0 me-3">Zasada łączenia warunków:</p>
<select class="form-select form-select-sm w-auto border-primary text-primary fw-bold">
<option value="AND">Spełnione WSZYSTKIE (AND)</option>
<option value="OR">Spełniony DOWOLNY (OR)</option>
</select>
</div>
<p class="small text-muted fw-bold mb-2">Warunki (Wyzwalacze):</p>
<div class="conditions-container mb-3">
<div class="row align-items-end mb-2 condition-row">
<div class="col-md-3">
<label class="form-label small">Parametr</label>
<select class="form-select form-select-sm">
<option>Poziom gazu (%)</option>
<option>Ciśnienie (bar)</option>
<option>Temperatura (°C)</option>
<option>Brak sygnału</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label small">Znak</label>
<select class="form-select form-select-sm">
<option value="<">Mniejszy niż (&lt;)</option>
<option value=">">Większy niż (&gt;)</option>
<option value="=">Równy (=)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label small">Wartość</label>
<input type="number" step="0.1" class="form-control form-control-sm">
</div>
<div class="col-md-2 text-end">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.condition-row').remove()">
<i class="bx bx-x"></i> Usuń
</button>
</div>
</div>
</div>
<button type="button" class="btn btn-sm btn-label-secondary mb-4" onclick="addCondition(this)">
<i class="bx bx-plus"></i> Dodaj warunek
</button>
<hr>
<p class="small text-muted fw-bold mb-2">Akcje po spełnieniu warunków:</p>
<div class="actions-container mb-3">
<div class="row align-items-end mb-2 action-row">
<div class="col-md-4">
<label class="form-label small">Kanał wysyłki</label>
<select class="form-select form-select-sm">
<option>E-mail</option>
<option>SMS</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label small">Odbiorca (Numer lub Email)</label>
<input type="text" class="form-control form-control-sm">
</div>
<div class="col-md-2 text-end">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.action-row').remove()">
<i class="bx bx-x"></i> Usuń
</button>
</div>
</div>
</div>
<button type="button" class="btn btn-sm btn-label-secondary" onclick="addActionChannel(this)">
<i class="bx bx-plus"></i> Dodaj kanał
</button>
</div>
</div>
</div>
<div class="d-flex justify-content-between mt-4 border-top pt-4">
<button type="button" class="btn btn-outline-secondary"
onclick="goToStep1()">Wróć</button>
<button type="button" class="btn btn-primary" onclick="goToStep3()">Zapisz i Zakończ</button>
</div>
</div>
<!-- KROK 3 -->
<div id="wizard-step-3" class="wizard-content d-none text-center py-4">
<div
class="avatar avatar-xl bg-label-success mx-auto mb-4 rounded-circle d-flex align-items-center justify-content-center">
<i class="bx bx-check fs-1"></i>
</div>
<h4 class="mb-2">Instalacja zarejestrowana!</h4>
<p class="text-muted mb-4">Instalacja została pomyślnie dodana do ekosystemu.</p>
<div class="bg-lighter rounded p-5 mx-auto mb-4 border border-dashed text-center" style="max-width: 600px;">
<i class="bx bx-cog fs-1 text-muted mb-3"></i>
<h6 class="text-muted text-uppercase mb-2">Miejsce na dane uwierzytelniające</h6>
<p class="small text-muted mb-0">W zależności od ostatecznych ustaleń architektonicznych, w tym miejscu system wygeneruje dla Ciebie <strong>Token API</strong> lub <strong>Gotowy plik konfiguracyjny</strong> do załadowania na sprzęt.</p>
</div>
<a href="installations.php" class="btn btn-primary mt-2">Przejdź do listy instalacji</a>
</div>
</div>
</div>
</div>
<div class="content-backdrop fade"></div>
<?php include '../../footer.php'; ?>
<script>
// Prosta logika wizarda
function goToStep1() {
document.querySelectorAll('.wizard-content').forEach(el => el.classList.add('d-none'));
document.getElementById('wizard-step-1').classList.remove('d-none');
updateTabsUI(1);
}
function goToStep2(type) {
document.querySelectorAll('.wizard-content').forEach(el => el.classList.add('d-none'));
document.getElementById('wizard-step-2').classList.remove('d-none');
// Pokaż odpowiednie pola
document.querySelectorAll('.dynamic-form-part').forEach(el => el.classList.add('d-none'));
if (type === 'gas') {
document.getElementById('form-gas').classList.remove('d-none');
document.getElementById('type-label-name').innerText = 'Zbiornik Gazu';
} else {
document.getElementById('form-farm').classList.remove('d-none');
document.getElementById('type-label-name').innerText = 'Sterownik Mikroklimatu';
}
updateTabsUI(2);
}
function goToStep3() {
document.querySelectorAll('.wizard-content').forEach(el => el.classList.add('d-none'));
document.getElementById('wizard-step-3').classList.remove('d-none');
updateTabsUI(3);
}
function updateTabsUI(step) {
// Reset
document.querySelectorAll('.wizard-step').forEach(el => {
el.classList.remove('active-step');
el.classList.add('opacity-50');
el.querySelector('.badge').classList.replace('bg-primary', 'bg-secondary');
el.querySelector('h6').classList.remove('text-primary', 'fw-bold');
el.querySelector('h6').classList.add('text-muted');
});
// Set active
let activeTab = document.getElementById('wizard-tab-' + step);
activeTab.classList.remove('opacity-50');
activeTab.classList.add('active-step');
activeTab.querySelector('.badge').classList.replace('bg-secondary', 'bg-primary');
activeTab.querySelector('h6').classList.remove('text-muted');
activeTab.querySelector('h6').classList.add('text-primary', 'fw-bold');
}
function copyToken() {
var copyText = document.getElementById("apiTokenInput");
copyText.select();
copyText.setSelectionRange(0, 99999);
navigator.clipboard.writeText(copyText.value);
alert("Token skopiowany do schowka!");
}
// Modal Przypisywania Urządzenia
function openAssignModal() {
var myModal = new bootstrap.Modal(document.getElementById('assignDeviceModal'));
myModal.show();
}
function addAlertBlock() {
const container = document.getElementById('alerts-container');
const alertHTML = `
<div class="card bg-lighter border mb-4 alert-block">
<div class="card-header border-bottom d-flex justify-content-between align-items-center py-3">
<h6 class="mb-0 text-danger"><i class="bx bx-bell"></i> Nowy Alert</h6>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.alert-block').remove()"><i class="bx bx-trash"></i> Usuń ten alert</button>
</div>
<div class="card-body pt-3 pb-2">
<div class="d-flex align-items-center mb-3">
<p class="small text-muted fw-bold mb-0 me-3">Zasada łączenia warunków:</p>
<select class="form-select form-select-sm w-auto border-primary text-primary fw-bold">
<option value="AND">Spełnione WSZYSTKIE (AND)</option>
<option value="OR">Spełniony DOWOLNY (OR)</option>
</select>
</div>
<p class="small text-muted fw-bold mb-2">Warunki (Wyzwalacze):</p>
<div class="conditions-container mb-3">
<div class="row align-items-end mb-2 condition-row">
<div class="col-md-3">
<label class="form-label small">Parametr</label>
<select class="form-select form-select-sm">
<option>Poziom gazu (%)</option>
<option>Ciśnienie (bar)</option>
<option>Temperatura (°C)</option>
<option>Brak sygnału</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label small">Znak</label>
<select class="form-select form-select-sm">
<option value="<">Mniejszy niż (&lt;)</option>
<option value=">">Większy niż (&gt;)</option>
<option value="=">Równy (=)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label small">Wartość</label>
<input type="number" step="0.1" class="form-control form-control-sm">
</div>
<div class="col-md-2 text-end">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.condition-row').remove()">
<i class="bx bx-x"></i> Usuń
</button>
</div>
</div>
</div>
<button type="button" class="btn btn-sm btn-label-secondary mb-4" onclick="addCondition(this)">
<i class="bx bx-plus"></i> Dodaj warunek
</button>
<hr>
<p class="small text-muted fw-bold mb-2">Akcje po spełnieniu warunków:</p>
<div class="actions-container mb-3">
<div class="row align-items-end mb-2 action-row">
<div class="col-md-4">
<label class="form-label small">Kanał wysyłki</label>
<select class="form-select form-select-sm">
<option>E-mail</option>
<option>SMS</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label small">Odbiorca (Numer lub Email)</label>
<input type="text" class="form-control form-control-sm">
</div>
<div class="col-md-2 text-end">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.action-row').remove()">
<i class="bx bx-x"></i> Usuń
</button>
</div>
</div>
</div>
<button type="button" class="btn btn-sm btn-label-secondary" onclick="addActionChannel(this)">
<i class="bx bx-plus"></i> Dodaj kanał
</button>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', alertHTML);
}
function addCondition(btn) {
const conditionsContainer = btn.previousElementSibling;
const conditionHTML = `
<div class="row align-items-end mb-2 condition-row">
<div class="col-md-3">
<label class="form-label small">Parametr</label>
<select class="form-select form-select-sm">
<option>Poziom gazu (%)</option>
<option>Ciśnienie (bar)</option>
<option>Temperatura (°C)</option>
<option>Brak sygnału</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label small">Znak</label>
<select class="form-select form-select-sm">
<option value="<">Mniejszy niż (&lt;)</option>
<option value=">">Większy niż (&gt;)</option>
<option value="=">Równy (=)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label small">Wartość</label>
<input type="number" step="0.1" class="form-control form-control-sm">
</div>
<div class="col-md-2 text-end">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.condition-row').remove()">
<i class="bx bx-x"></i> Usuń
</button>
</div>
</div>
`;
conditionsContainer.insertAdjacentHTML('beforeend', conditionHTML);
}
function addActionChannel(btn) {
const actionsContainer = btn.previousElementSibling;
const actionHTML = `
<div class="row align-items-end mb-2 action-row">
<div class="col-md-4">
<label class="form-label small">Kanał wysyłki</label>
<select class="form-select form-select-sm">
<option>E-mail</option>
<option>SMS</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label small">Odbiorca (Numer lub Email)</label>
<input type="text" class="form-control form-control-sm">
</div>
<div class="col-md-2 text-end">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.action-row').remove()">
<i class="bx bx-x"></i> Usuń
</button>
</div>
</div>
`;
actionsContainer.insertAdjacentHTML('beforeend', actionHTML);
}
</script>
<style>
.hover-border-primary:hover {
border-color: #696cff !important;
transition: 0.3s;
}
</style>
<!-- Modal Przypisywania Urządzenia -->
<div class="modal fade" id="assignDeviceModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Przypisz urządzenie do instalacji</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Wybierz z magazynu (tylko wolne urządzenia)</label>
<select class="form-select">
<option value="">-- Wybierz urządzenie --</option>
<option value="1027">1027 - Nowy czujnik testowy</option>
<option value="1030">1030 - Moduł WiFi Zewnętrzny</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Anuluj</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Przypisz urządzenie</button>
</div>
</div>
</div>
</div>
</body>
</html>
+429
View File
@@ -0,0 +1,429 @@
<?php
$enablePrototypeComments = true;
include '../../header-telemetria.php';
?>
<div class="container-xxl flex-grow-1 container-p-y">
<h4 class="fw-bold py-3 mb-4">
Telemetria <span class="text-muted fw-light">/ Edycja instalacji</span>
</h4>
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center mb-4">
<div class="avatar avatar-lg bg-label-warning me-3">
<i class="bx bxs-flame fs-3"></i>
</div>
<div>
<h5 class="mb-0">Edytujesz: Zbiornik LPG #1</h5>
<small class="text-muted">Typ: Zbiornik Gazu (LPG/CNG) | ID: mag_tlm_837492</small>
</div>
</div>
<!-- Formularz wspólny -->
<div class="row mb-3">
<div class="col-md-6 mb-3">
<label class="form-label">Nazwa instalacji (Własna)</label>
<input type="text" class="form-control" value="Zbiornik LPG #1 (Stalowa Wola)">
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Lokalizacja / Adres</label>
<input type="text" class="form-control" value="Stalowa Wola, ul. Przemysłowa">
</div>
</div>
<hr>
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h6 class="text-muted mb-1">Przypięte Urządzenia (Hardware)</h6>
<p class="small text-muted mb-0">Wybierz fizyczne urządzenia i skonfiguruj, jakie parametry mają przesyłać.</p>
</div>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#assignDeviceModal">
<i class="bx bx-link"></i> Przypisz urządzenie
</button>
</div>
<div class="card bg-lighter border mb-4">
<div class="card-header border-bottom d-flex justify-content-between align-items-center py-3">
<div class="d-flex align-items-center">
<div class="avatar avatar-sm bg-white border me-2 d-flex justify-content-center align-items-center">
<i class="bx bx-chip text-primary"></i>
</div>
<h6 class="mb-0">Nadajnik Główny #1 <span class="text-muted fw-normal ms-1">(ID: 1024, MAC: 00:1B:44:11:3A:B7)</span></h6>
</div>
<button class="btn btn-sm btn-outline-danger"><i class="bx bx-unlink"></i> Odłącz</button>
</div>
<div class="card-body pt-3 pb-0">
<p class="small text-muted mb-2">Parametry obsługiwane przez to urządzenie:</p>
<div class="table-responsive text-nowrap mb-2">
<table class="table table-sm table-bordered bg-white">
<thead class="table-light">
<tr>
<th>Klucz</th>
<th>Nazwa</th>
<th>Ostatnia wartość</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="text-monospace text-muted">gas_level</span></td>
<td>Poziom napełnienia</td>
<td><span class="badge bg-label-primary">45%</span></td>
</tr>
<tr>
<td><span class="text-monospace text-muted">gas_pressure</span></td>
<td>Ciśnienie</td>
<td><span class="badge bg-label-info">12.5 bar</span></td>
</tr>
<tr>
<td><span class="text-monospace text-muted">gas_temp</span></td>
<td>Temperatura</td>
<td><span class="badge bg-label-warning">15.2 °C</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card bg-lighter border mb-4">
<div class="card-header border-bottom d-flex justify-content-between align-items-center py-3">
<div class="d-flex align-items-center">
<div class="avatar avatar-sm bg-white border me-2 d-flex justify-content-center align-items-center">
<i class="bx bx-chip text-primary"></i>
</div>
<h6 class="mb-0">Nadajnik Zapasowy #2 <span class="text-muted fw-normal ms-1">(ID: 1025, MAC: 00:1B:44:11:4F:C1)</span></h6>
</div>
<button class="btn btn-sm btn-outline-danger"><i class="bx bx-unlink"></i> Odłącz</button>
</div>
<div class="card-body pt-3 pb-0">
<p class="small text-muted mb-2">Parametry obsługiwane przez to urządzenie:</p>
<div class="table-responsive text-nowrap mb-2">
<table class="table table-sm table-bordered bg-white">
<thead class="table-light">
<tr>
<th>Klucz</th>
<th>Nazwa</th>
<th>Ostatnia wartość</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="text-monospace text-muted">gps_lat</span></td>
<td>Szerokość geogr. (GPS)</td>
<td><span class="badge bg-label-secondary">Brak sygnału</span></td>
</tr>
<tr>
<td><span class="text-monospace text-muted">gps_lon</span></td>
<td>Długość geogr. (GPS)</td>
<td><span class="badge bg-label-secondary">Brak sygnału</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Konfigurator Powiadomień -->
<hr class="my-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="text-muted mb-0">Konfigurator powiadomień (Alerty)</h6>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="addAlertBlock()">
<i class="bx bx-plus"></i> Dodaj alert
</button>
</div>
<div id="alerts-container">
<!-- Pojedynczy blok alertu -->
<div class="card bg-lighter border mb-4 alert-block">
<div class="card-header border-bottom d-flex justify-content-between align-items-center py-3">
<h6 class="mb-0 text-danger"><i class="bx bx-bell"></i> Alert Systemowy #1</h6>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.alert-block').remove()"><i class="bx bx-trash"></i> Usuń ten alert</button>
</div>
<div class="card-body pt-3 pb-2">
<div class="d-flex align-items-center mb-3">
<p class="small text-muted fw-bold mb-0 me-3">Zasada łączenia warunków:</p>
<select class="form-select form-select-sm w-auto border-primary text-primary fw-bold">
<option value="AND">Spełnione WSZYSTKIE (AND)</option>
<option value="OR">Spełniony DOWOLNY (OR)</option>
</select>
</div>
<p class="small text-muted fw-bold mb-2">Warunki (Wyzwalacze):</p>
<div class="conditions-container mb-3">
<div class="row align-items-end mb-2 condition-row">
<div class="col-md-3">
<label class="form-label small">Parametr</label>
<select class="form-select form-select-sm">
<option selected>Poziom gazu (%)</option>
<option>Ciśnienie (bar)</option>
<option>Temperatura (°C)</option>
<option>Brak sygnału</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label small">Znak</label>
<select class="form-select form-select-sm">
<option value="<" selected>Mniejszy niż (&lt;)</option>
<option value=">">Większy niż (&gt;)</option>
<option value="=">Równy (=)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label small">Wartość</label>
<input type="number" step="0.1" class="form-control form-control-sm" value="15">
</div>
<div class="col-md-2 text-end">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.condition-row').remove()">
<i class="bx bx-x"></i> Usuń
</button>
</div>
</div>
<div class="row align-items-end mb-2 condition-row">
<div class="col-md-3">
<label class="form-label small">Parametr</label>
<select class="form-select form-select-sm">
<option>Poziom gazu (%)</option>
<option selected>Ciśnienie (bar)</option>
<option>Temperatura (°C)</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label small">Znak</label>
<select class="form-select form-select-sm">
<option value="<">Mniejszy niż (&lt;)</option>
<option value=">" selected>Większy niż (&gt;)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label small">Wartość</label>
<input type="number" step="0.1" class="form-control form-control-sm" value="10">
</div>
<div class="col-md-2 text-end">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.condition-row').remove()">
<i class="bx bx-x"></i> Usuń
</button>
</div>
</div>
</div>
<button type="button" class="btn btn-sm btn-label-secondary mb-4" onclick="addCondition(this)">
<i class="bx bx-plus"></i> Dodaj warunek
</button>
<hr>
<p class="small text-muted fw-bold mb-2">Akcje po spełnieniu warunków:</p>
<div class="actions-container mb-3">
<div class="row align-items-end mb-2 action-row">
<div class="col-md-4">
<label class="form-label small">Kanał wysyłki</label>
<select class="form-select form-select-sm">
<option>E-mail</option>
<option selected>SMS</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label small">Odbiorca (Numer lub Email)</label>
<input type="text" class="form-control form-control-sm" value="+48 123 456 789">
</div>
<div class="col-md-2 text-end">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.action-row').remove()">
<i class="bx bx-x"></i> Usuń
</button>
</div>
</div>
</div>
<button type="button" class="btn btn-sm btn-label-secondary" onclick="addActionChannel(this)">
<i class="bx bx-plus"></i> Dodaj kanał
</button>
</div>
</div>
</div>
<div class="d-flex justify-content-between mt-5 border-top pt-4">
<a href="installations.php" class="btn btn-outline-secondary">Anuluj</a>
<a href="installations.php" class="btn btn-primary">Zapisz zmiany</a>
</div>
</div>
</div>
</div>
<div class="content-backdrop fade"></div>
<?php include '../../footer.php'; ?>
<script>
function addAlertBlock() {
const container = document.getElementById('alerts-container');
const alertHTML = `
<div class="card bg-lighter border mb-4 alert-block">
<div class="card-header border-bottom d-flex justify-content-between align-items-center py-3">
<h6 class="mb-0 text-danger"><i class="bx bx-bell"></i> Nowy Alert</h6>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.alert-block').remove()"><i class="bx bx-trash"></i> Usuń ten alert</button>
</div>
<div class="card-body pt-3 pb-2">
<div class="d-flex align-items-center mb-3">
<p class="small text-muted fw-bold mb-0 me-3">Zasada łączenia warunków:</p>
<select class="form-select form-select-sm w-auto border-primary text-primary fw-bold">
<option value="AND">Spełnione WSZYSTKIE (AND)</option>
<option value="OR">Spełniony DOWOLNY (OR)</option>
</select>
</div>
<p class="small text-muted fw-bold mb-2">Warunki (Wyzwalacze):</p>
<div class="conditions-container mb-3">
<div class="row align-items-end mb-2 condition-row">
<div class="col-md-3">
<label class="form-label small">Parametr</label>
<select class="form-select form-select-sm">
<option>Poziom gazu (%)</option>
<option>Ciśnienie (bar)</option>
<option>Temperatura (°C)</option>
<option>Brak sygnału</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label small">Znak</label>
<select class="form-select form-select-sm">
<option value="<">Mniejszy niż (&lt;)</option>
<option value=">">Większy niż (&gt;)</option>
<option value="=">Równy (=)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label small">Wartość</label>
<input type="number" step="0.1" class="form-control form-control-sm">
</div>
<div class="col-md-2 text-end">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.condition-row').remove()">
<i class="bx bx-x"></i> Usuń
</button>
</div>
</div>
</div>
<button type="button" class="btn btn-sm btn-label-secondary mb-4" onclick="addCondition(this)">
<i class="bx bx-plus"></i> Dodaj warunek
</button>
<hr>
<p class="small text-muted fw-bold mb-2">Akcje po spełnieniu warunków:</p>
<div class="actions-container mb-3">
<div class="row align-items-end mb-2 action-row">
<div class="col-md-4">
<label class="form-label small">Kanał wysyłki</label>
<select class="form-select form-select-sm">
<option>E-mail</option>
<option>SMS</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label small">Odbiorca (Numer lub Email)</label>
<input type="text" class="form-control form-control-sm">
</div>
<div class="col-md-2 text-end">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.action-row').remove()">
<i class="bx bx-x"></i> Usuń
</button>
</div>
</div>
</div>
<button type="button" class="btn btn-sm btn-label-secondary" onclick="addActionChannel(this)">
<i class="bx bx-plus"></i> Dodaj kanał
</button>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', alertHTML);
}
function addCondition(btn) {
const conditionsContainer = btn.previousElementSibling;
const conditionHTML = `
<div class="row align-items-end mb-2 condition-row">
<div class="col-md-3">
<label class="form-label small">Parametr</label>
<select class="form-select form-select-sm">
<option>Poziom gazu (%)</option>
<option>Ciśnienie (bar)</option>
<option>Temperatura (°C)</option>
<option>Brak sygnału</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label small">Znak</label>
<select class="form-select form-select-sm">
<option value="<">Mniejszy niż (&lt;)</option>
<option value=">">Większy niż (&gt;)</option>
<option value="=">Równy (=)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label small">Wartość</label>
<input type="number" step="0.1" class="form-control form-control-sm">
</div>
<div class="col-md-2 text-end">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.condition-row').remove()">
<i class="bx bx-x"></i> Usuń
</button>
</div>
</div>
`;
conditionsContainer.insertAdjacentHTML('beforeend', conditionHTML);
}
function addActionChannel(btn) {
const actionsContainer = btn.previousElementSibling;
const actionHTML = `
<div class="row align-items-end mb-2 action-row">
<div class="col-md-4">
<label class="form-label small">Kanał wysyłki</label>
<select class="form-select form-select-sm">
<option>E-mail</option>
<option>SMS</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label small">Odbiorca (Numer lub Email)</label>
<input type="text" class="form-control form-control-sm">
</div>
<div class="col-md-2 text-end">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.action-row').remove()">
<i class="bx bx-x"></i> Usuń
</button>
</div>
</div>
`;
actionsContainer.insertAdjacentHTML('beforeend', actionHTML);
}
</script>
<!-- Modal Przypisywania Urządzenia -->
<div class="modal fade" id="assignDeviceModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Przypisz urządzenie do instalacji</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Wybierz z magazynu (tylko wolne urządzenia)</label>
<select class="form-select">
<option value="">-- Wybierz urządzenie --</option>
<option value="1027">1027 - Nowy czujnik testowy</option>
<option value="1030">1030 - Moduł WiFi Zewnętrzny</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Anuluj</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Przypisz urządzenie</button>
</div>
</div>
</div>
</div>
</body>
</html>
+441
View File
@@ -0,0 +1,441 @@
<?php
$enablePrototypeComments = true;
include '../../header-telemetria.php';
?>
<div class="container-xxl flex-grow-1 container-p-y">
<div class="d-flex justify-content-between align-items-center py-3 mb-4">
<h4 class="fw-bold mb-0">
Telemetria <span class="text-muted fw-light">/ Podgląd Live</span>
</h4>
<div class="d-flex align-items-center">
<span class="badge bg-success me-2 px-3 py-2 fs-6"><i class="bx bx-wifi"></i> ONLINE</span>
<small class="text-muted d-none d-md-block">Ostatni kontakt: Dzisiaj, 14:52:10</small>
</div>
</div>
<!-- Header z wyborem instalacji -->
<div class="card mb-4">
<div class="card-body d-flex flex-column flex-md-row justify-content-between align-items-md-center">
<div class="d-flex align-items-center mb-3 mb-md-0">
<div class="avatar avatar-lg bg-label-warning me-3">
<i class="bx bxs-flame fs-3"></i>
</div>
<div>
<h5 class="mb-0">Zbiornik LPG #1 (Stalowa Wola)</h5>
<small class="text-muted">ID: mag_tlm_837492</small>
</div>
</div>
<div>
<button type="button" class="btn btn-outline-primary me-2" id="toggleEditModeBtn" onclick="toggleEditMode()">
<i class="bx bx-customize me-1"></i> Edytuj dashboard
</button>
<a href="installation-edit.php" class="btn btn-outline-secondary">
<i class="bx bx-pencil me-1"></i> Edytuj instalację
</a>
</div>
</div>
</div>
<div class="row g-4 mb-4">
<!-- SVG Zbiornika -->
<div class="col-xl-4 col-lg-5 col-md-12">
<div class="card border-0 shadow-sm mb-4 position-relative">
<button type="button" class="btn btn-sm btn-icon btn-primary position-absolute top-0 end-0 m-2 widget-settings-btn d-none zindex-1" onclick="openWidgetConfig('Wizualizacja stanu napełnienia')">
<i class="bx bx-cog"></i>
</button>
<div class="card-header border-bottom">
<h5 class="card-title m-0">Wizualizacja stanu napełnienia</h5>
</div>
<div class="card-body d-flex flex-column align-items-center justify-content-center p-5">
<!-- SVG Silos -->
<div class="tank-container position-relative mb-3 mx-auto" style="width: 140px; height: 350px;">
<svg viewBox="0 0 100 300" width="100%" height="100%">
<!-- Nogi silosu -->
<path d="M 35 260 L 30 295 M 65 260 L 70 295" stroke="#a1acb8" stroke-width="6"
stroke-linecap="round" />
<line x1="20" y1="295" x2="80" y2="295" stroke="#a1acb8" stroke-width="4"
stroke-linecap="round" />
<!-- Zewnętrzna krawędź obudowy (Silos stojący) -->
<path id="siloOutline" d="M 20 40 A 30 20 0 0 1 80 40 L 80 250 A 30 20 0 0 1 20 250 Z"
fill="#f8f9fa" stroke="#d9dee3" stroke-width="4" />
<!-- Animowane wypełnienie (Mask/Clip) -->
<clipPath id="fillClip">
<path d="M 22 42 A 28 18 0 0 1 78 42 L 78 248 A 28 18 0 0 1 22 248 Z" />
</clipPath>
<!-- Płyn - gaz -->
<defs>
<linearGradient id="gasGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#ffab00" />
<stop offset="100%" stop-color="#e69a00" />
</linearGradient>
</defs>
<!-- Ten prostokąt porusza się w górę i w dół, maskowany przez kształt silosu -->
<g clip-path="url(#fillClip)">
<rect id="liquidFill" x="0" y="120" width="100" height="250" fill="url(#gasGradient)" />
</g>
<!-- Pierścienie wzmacniające silosu -->
<path d="M 20 90 A 30 10 0 0 0 80 90" stroke="#d9dee3" stroke-width="2" fill="none" />
<path d="M 20 150 A 30 10 0 0 0 80 150" stroke="#d9dee3" stroke-width="2" fill="none" />
<path d="M 20 210 A 30 10 0 0 0 80 210" stroke="#d9dee3" stroke-width="2" fill="none" />
<!-- Efekt szkła/odblasku dla głębi 3D -->
<path d="M 26 50 Q 35 150 26 240" stroke="#ffffff" stroke-width="4" fill="none"
opacity="0.6" stroke-linecap="round" />
<path d="M 74 50 Q 65 150 74 240" stroke="#000000" stroke-width="3" fill="none"
opacity="0.05" stroke-linecap="round" />
<!-- Podziałka na zbiorniku -->
<line x1="85" y1="90" x2="95" y2="90" stroke="#a1acb8" stroke-width="2" />
<text x="80" y="93" font-size="8" fill="#a1acb8" text-anchor="end">75%</text>
<line x1="85" y1="150" x2="95" y2="150" stroke="#a1acb8" stroke-width="2" />
<text x="80" y="153" font-size="8" fill="#a1acb8" text-anchor="end">50%</text>
<line x1="85" y1="210" x2="95" y2="210" stroke="#a1acb8" stroke-width="2" />
<text x="80" y="213" font-size="8" fill="#a1acb8" text-anchor="end">25%</text>
</svg>
<!-- Główna etykieta procentowa nałożona na grafikę -->
<div class="position-absolute top-50 start-50 translate-middle text-center"
style="margin-top: -15px;">
<h1 class="mb-0 fw-bolder text-dark"
style="text-shadow: 0px 0px 10px rgba(255,255,255,0.8);" id="mainPercentage">60%</h1>
</div>
</div>
</div>
</div>
<div class="card shadow-sm border-0">
<div class="card-header border-bottom d-flex justify-content-between align-items-center">
<h5 class="card-title m-0">Skonfigurowane alerty</h5>
<button class="btn btn-sm btn-icon btn-text-secondary"><i class="bx bx-cog"></i></button>
</div>
<div class="card-body pt-4">
<ul class="list-unstyled mb-0">
<li class="mb-3 d-flex align-items-center">
<span class="badge bg-label-danger me-3 p-2"><i class="bx bx-down-arrow-alt"></i></span>
<div>
<h6 class="mb-0">Niski poziom gazu</h6>
<small class="text-muted">Gdy poziom &lt; 15%</small>
</div>
</li>
<li class="mb-3 d-flex align-items-center">
<span class="badge bg-label-danger me-3 p-2"><i class="bx bx-up-arrow-alt"></i></span>
<div>
<h6 class="mb-0">Nadciśnienie</h6>
<small class="text-muted">Gdy ciśnienie &gt; 7.5 bar</small>
</div>
</li>
<li class="d-flex align-items-center">
<span class="badge bg-label-warning me-3 p-2"><i class="bx bx-wifi-off"></i></span>
<div>
<h6 class="mb-0">Brak łączności</h6>
<small class="text-muted">Gdy brak sygnału &gt; 10 min</small>
</div>
</li>
</ul>
</div>
</div>
</div>
<!-- Parametry pomiarowe - Kafeleki -->
<div class="col-xl-8 col-lg-7 col-md-12">
<div class="row row-cols-1 row-cols-sm-2 g-4 align-content-start mb-4">
<!-- Poziom gazu w litrach -->
<div class="col">
<div class="card shadow-sm border-0 border-start border-warning border-3 position-relative">
<button type="button" class="btn btn-sm btn-icon btn-primary position-absolute top-0 end-0 m-2 widget-settings-btn d-none zindex-1" onclick="openWidgetConfig('Poziom gazu (Wartość / litry)')">
<i class="bx bx-cog"></i>
</button>
<div class="card-body">
<div class="d-flex align-items-start justify-content-between">
<div class="content-left">
<span class="text-muted text-uppercase small fw-bold"
style="letter-spacing: 0.5px;">Szacowana objętość</span>
<div class="d-flex align-items-end mt-2">
<h2 class="mb-0 me-2 text-dark" id="volumeValue">1 620</h2>
<span class="fs-6 fw-normal text-muted mb-1">litrów</span>
</div>
<small class="text-success fw-semibold"><i class="bx bx-up-arrow-alt"></i> +25 l w
ciągu 5 min</small>
</div>
<span class="badge bg-label-warning rounded p-2">
<i class="bx bxs-flame fs-3"></i>
</span>
</div>
</div>
</div>
</div>
<!-- Ciśnienie -->
<div class="col">
<div class="card shadow-sm border-0 position-relative">
<button type="button" class="btn btn-sm btn-icon btn-primary position-absolute top-0 end-0 m-2 widget-settings-btn d-none zindex-1" onclick="openWidgetConfig('Ciśnienie operacyjne')">
<i class="bx bx-cog"></i>
</button>
<div class="card-body">
<div class="d-flex align-items-start justify-content-between">
<div class="content-left">
<span class="text-muted text-uppercase small fw-bold"
style="letter-spacing: 0.5px;">Ciśnienie operacyjne</span>
<div class="d-flex align-items-end mt-2">
<h2 class="mb-0 me-2 text-dark" id="pressureValue">5.24</h2>
<span class="fs-6 fw-normal text-muted mb-1">bar</span>
</div>
<small class="text-muted">Norma: 3.0 - 7.0 bar</small>
</div>
<span class="badge bg-label-info rounded p-2">
<i class="bx bx-tachometer fs-3"></i>
</span>
</div>
</div>
</div>
</div>
<!-- Temperatura -->
<div class="col">
<div class="card shadow-sm border-0 position-relative">
<button type="button" class="btn btn-sm btn-icon btn-primary position-absolute top-0 end-0 m-2 widget-settings-btn d-none zindex-1" onclick="openWidgetConfig('Temperatura otoczenia')">
<i class="bx bx-cog"></i>
</button>
<div class="card-body">
<div class="d-flex align-items-start justify-content-between">
<div class="content-left">
<span class="text-muted text-uppercase small fw-bold"
style="letter-spacing: 0.5px;">Temperatura otoczenia</span>
<div class="d-flex align-items-end mt-2">
<h2 class="mb-0 me-2 text-dark">14.5</h2>
<span class="fs-6 fw-normal text-muted mb-1">°C</span>
</div>
<small class="text-muted">Trend stabilny</small>
</div>
<span class="badge bg-label-primary rounded p-2">
<i class="bx bxs-thermometer fs-3"></i>
</span>
</div>
</div>
</div>
</div>
<!-- Zasilanie i sieć -->
<div class="col">
<div class="card shadow-sm border-0 position-relative">
<button type="button" class="btn btn-sm btn-icon btn-primary position-absolute top-0 end-0 m-2 widget-settings-btn d-none zindex-1" onclick="openWidgetConfig('Hardware & Sieć')">
<i class="bx bx-cog"></i>
</button>
<div class="card-body">
<div class="d-flex align-items-start justify-content-between mb-3">
<span class="text-muted text-uppercase small fw-bold"
style="letter-spacing: 0.5px;">Hardware & Sieć</span>
<span class="badge bg-label-secondary rounded p-2"><i
class="bx bx-chip fs-4"></i></span>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted"><i class="bx bx-battery me-1"></i> Bateria
zew.</span>
<span class="fw-semibold">12.4 V</span>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted"><i class="bx bx-signal-4 me-1"></i> Sygnał
GSM</span>
<span class="fw-semibold">82%</span>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted"><i class="bx bx-time-five me-1"></i> Ostatni
kontakt</span>
<span class="fw-semibold">14:52:10</span>
</div>
</div>
</div>
</div>
</div>
<div class="card shadow-sm border-0">
<div class="card-header border-bottom">
<h5 class="card-title m-0">Historia ostatnich odczytów</h5>
</div>
<div class="card-body pt-4 pb-0" style="max-height: 400px; overflow-y: auto;">
<ul class="timeline mb-0">
<li class="timeline-item timeline-item-transparent">
<span class="timeline-point timeline-point-primary"></span>
<div class="timeline-event">
<div class="timeline-header mb-1">
<h6 class="mb-0">Regularny odczyt</h6>
<small class="text-muted">Dzisiaj, 14:52:10</small>
</div>
<p class="mb-0 small">Poziom: 60% (1620 l) | Ciśnienie: 5.24 bar | Bateria: 12.4V</p>
</div>
</li>
<li class="timeline-item timeline-item-transparent">
<span class="timeline-point timeline-point-danger"></span>
<div class="timeline-event">
<div class="timeline-header mb-1">
<h6 class="mb-0 text-danger">ALERT: Zbyt wysokie ciśnienie!</h6>
<small class="text-muted">Dzisiaj, 13:10:05</small>
</div>
<p class="mb-0 small">Zarejestrowano skok ciśnienia: <strong>7.8 bar</strong> (Próg: 7.5
bar). Wysłano powiadomienie SMS do serwisu.</p>
</div>
</li>
<li class="timeline-item timeline-item-transparent">
<span class="timeline-point timeline-point-primary"></span>
<div class="timeline-event">
<div class="timeline-header mb-1">
<h6 class="mb-0">Regularny odczyt</h6>
<small class="text-muted">Dzisiaj, 12:52:10</small>
</div>
<p class="mb-0 small">Poziom: 61% (1640 l) | Ciśnienie: 6.8 bar | Bateria: 12.4V</p>
</div>
</li>
<li class="timeline-item timeline-item-transparent">
<span class="timeline-point timeline-point-primary"></span>
<div class="timeline-event">
<div class="timeline-header mb-1">
<h6 class="mb-0">Regularny odczyt</h6>
<small class="text-muted">Dzisiaj, 10:52:10</small>
</div>
<p class="mb-0 small">Poziom: 62% (1665 l) | Ciśnienie: 6.5 bar | Bateria: 12.5V</p>
</div>
</li>
<li class="timeline-item timeline-item-transparent">
<span class="timeline-point timeline-point-info"></span>
<div class="timeline-event">
<div class="timeline-header mb-1">
<h6 class="mb-0 text-info">Zakończono tankowanie</h6>
<small class="text-muted">Wczoraj, 18:30:00</small>
</div>
<p class="mb-0 small">Przyrost poziomu z 15% na 65%.</p>
</div>
</li>
<li class="timeline-item timeline-item-transparent border-transparent">
<span class="timeline-point timeline-point-primary"></span>
<div class="timeline-event">
<div class="timeline-header mb-1">
<h6 class="mb-0">Regularny odczyt</h6>
<small class="text-muted">Wczoraj, 16:52:10</small>
</div>
<p class="mb-0 small">Poziom: 14% (370 l) | Ciśnienie: 4.5 bar | Bateria: 12.5V</p>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<style>
/* CSS dla animacji zbiornika */
#liquidFill {
/* Symulacja falowania i wznoszenia płynu */
animation: fillTank 5s ease-in-out infinite alternate;
transform-origin: bottom;
}
@keyframes fillTank {
0% {
y: 135;
}
100% {
y: 125;
}
}
</style>
<script>
function toggleEditMode() {
document.body.classList.toggle('dashboard-edit-mode');
const isEdit = document.body.classList.contains('dashboard-edit-mode');
document.querySelectorAll('.widget-settings-btn').forEach(btn => {
if(isEdit) {
btn.classList.remove('d-none');
} else {
btn.classList.add('d-none');
}
});
const toggleBtn = document.getElementById('toggleEditModeBtn');
if(isEdit) {
toggleBtn.classList.replace('btn-outline-primary', 'btn-primary');
toggleBtn.innerHTML = '<i class="bx bx-check me-1"></i> Zakończ edycję';
} else {
toggleBtn.classList.replace('btn-primary', 'btn-outline-primary');
toggleBtn.innerHTML = '<i class="bx bx-customize me-1"></i> Edytuj dashboard';
}
}
function openWidgetConfig(widgetName) {
document.getElementById('configWidgetName').innerText = widgetName;
var myModal = new bootstrap.Modal(document.getElementById('widgetConfigModal'));
myModal.show();
}
// Prosty skrypt symulujący ruch danych (Live)
setInterval(() => {
// Losuj delikatnie ciśnienie
let currentPressure = parseFloat(document.getElementById('pressureValue').innerText);
let diff = (Math.random() * 0.04 - 0.02);
document.getElementById('pressureValue').innerText = (currentPressure + diff).toFixed(2);
// Czasami zaktualizuj litry
if (Math.random() > 0.5) {
let currentVol = parseInt(document.getElementById('volumeValue').innerText.replace(/\s/g, ''));
currentVol += Math.floor(Math.random() * 3);
document.getElementById('volumeValue').innerText = currentVol.toLocaleString('pl-PL');
}
}, 2000);
</script>
<!-- Modal Konfiguracji Widgetu -->
<div class="modal fade" id="widgetConfigModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Skonfiguruj widget: <br><span id="configWidgetName" class="text-primary fs-6 fw-bold"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Wybierz parametr z urządzenia</label>
<select class="form-select">
<option value="">-- Wybierz --</option>
<optgroup label="Urządzenie: 1027 - Moduł WiFi Zewnętrzny">
<option>Poziom gazu (%)</option>
<option>Ciśnienie (bar)</option>
<option>Temperatura otoczenia (°C)</option>
</optgroup>
<optgroup label="Urządzenie: 1030 - Moduł Zasilania">
<option>Napięcie baterii (V)</option>
<option>Zasięg GSM (%)</option>
</optgroup>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Anuluj</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Zapisz ustawienia</button>
</div>
</div>
</div>
</div>
<div class="content-backdrop fade"></div>
<?php include '../../footer.php'; ?>
</body>
</html>
+455
View File
@@ -0,0 +1,455 @@
<?php
$enablePrototypeComments = true;
include '../../header-telemetria.php';
?>
<link rel="stylesheet" href="<?= $basePath ?>/assets/vendor/libs/leaflet/leaflet.css" />
<style>
#installationsMap {
height: calc(100vh - 220px);
min-height: 480px;
width: 100%;
z-index: 1;
}
.map-sidebar-list {
max-height: calc(100vh - 420px);
min-height: 220px;
overflow-y: auto;
}
.map-marker-pin {
width: 32px;
height: 32px;
border-radius: 50% 50% 50% 0;
transform: rotate(-45deg);
border: 2px solid #fff;
box-shadow: 0 2px 6px rgba(67, 89, 113, 0.35);
display: flex;
align-items: center;
justify-content: center;
background-color: #696cff;
}
.map-marker-pin i {
transform: rotate(45deg);
color: #fff;
font-size: 14px;
line-height: 1;
}
.map-marker-pin.is-alert {
background-color: #ff3e1d;
}
.map-list-item {
cursor: pointer;
}
.map-list-item:hover,
.map-list-item.is-focused {
background-color: rgba(105, 108, 255, 0.08);
}
.map-list-item.is-hidden-on-map {
opacity: 0.55;
}
</style>
<div class="container-fluid flex-grow-1 container-p-y">
<h4 class="fw-bold py-3 mb-4">
Telemetria <span class="text-muted fw-light">/ Mapa instalacji</span>
</h4>
<div class="row g-4">
<div class="col-lg-8 col-xl-9">
<div class="card shadow-sm border-0 overflow-hidden">
<div id="installationsMap"></div>
</div>
</div>
<div class="col-lg-4 col-xl-3">
<div class="card shadow-sm border-0 h-100">
<div class="card-header border-bottom">
<h5 class="card-title mb-0">Instalacje na mapie</h5>
<small class="text-muted">Wyszukaj, filtruj i zarządzaj widocznością</small>
</div>
<div class="card-body">
<div class="mb-3 mt-3">
<label class="form-label" for="mapSearchInput">Szukaj</label>
<div class="input-group input-group-merge">
<span class="input-group-text"><i class="bx bx-search"></i></span>
<input type="text" id="mapSearchInput" class="form-control" placeholder="Nazwa lub lokalizacja...">
</div>
</div>
<div class="mb-3">
<label class="form-label" for="mapTypeFilter">Typ</label>
<select id="mapTypeFilter" class="form-select">
<option value="">Wszystkie typy</option>
<option value="gas">Zbiornik Gazu</option>
<option value="farm">Kurnik / Farma</option>
</select>
</div>
<div class="mb-3">
<label class="form-label" for="mapStatusFilter">Status</label>
<select id="mapStatusFilter" class="form-select">
<option value="">Każdy status</option>
<option value="online">Online</option>
<option value="alert">Alert</option>
<option value="offline">Offline</option>
</select>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="fw-medium">Widoczność</span>
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn btn-outline-primary" id="showAllInstallationsBtn">Wszystkie</button>
<button type="button" class="btn btn-outline-secondary" id="hideAllInstallationsBtn">Ukryj</button>
</div>
</div>
<div id="mapInstallationsList" class="map-sidebar-list border rounded"></div>
</div>
</div>
</div>
</div>
</div>
<?php include '../../footer.php'; ?>
<script src="<?= $basePath ?>/assets/vendor/libs/leaflet/leaflet.js"></script>
<script>
(function () {
const installations = [
{
id: 'inst-1',
name: 'Zbiornik LPG #1',
location: 'Stalowa Wola, ul. Przemysłowa 5',
type: 'gas',
typeLabel: 'Zbiornik Gazu',
status: 'online',
statusLabel: 'Online',
lat: 50.5826,
lng: 22.0534,
alert: null,
url: 'installation-live.php'
},
{
id: 'inst-2',
name: 'Kurnik B (Sektor 2)',
location: 'Farma Wola, Obiekt Główny',
type: 'farm',
typeLabel: 'Sterownik Kurnika',
status: 'online',
statusLabel: 'Online',
lat: 50.6012,
lng: 22.0918,
alert: null,
url: 'installation-live.php'
},
{
id: 'inst-3',
name: 'Zbiornik LPG #2',
location: 'Rzeszów, Baza Północ',
type: 'gas',
typeLabel: 'Zbiornik Gazu',
status: 'offline',
statusLabel: 'Offline',
lat: 50.0578,
lng: 22.0048,
alert: 'Brak łączności — brak sygnału od ponad 3 dni.',
url: 'installation-live.php'
},
{
id: 'inst-4',
name: 'Zbiornik LPG #3',
location: 'Lublin, ul. Metalurgiczna 12',
type: 'gas',
typeLabel: 'Zbiornik Gazu',
status: 'alert',
statusLabel: 'Alert',
lat: 51.2354,
lng: 22.5489,
alert: 'Niski poziom gazu — poziom poniżej 15%.',
url: 'installation-live.php'
},
{
id: 'inst-5',
name: 'Zbiornik LPG #4',
location: 'Tarnobrzeg, Stacja Przeładunkowa',
type: 'gas',
typeLabel: 'Zbiornik Gazu',
status: 'online',
statusLabel: 'Online',
lat: 50.5731,
lng: 21.6794,
alert: null,
url: 'installation-live.php'
},
{
id: 'inst-6',
name: 'Sterownik Farma Alpha',
location: 'Nisko, Sektor A',
type: 'farm',
typeLabel: 'Sterownik Kurnika',
status: 'online',
statusLabel: 'Online',
lat: 50.5198,
lng: 22.1396,
alert: null,
url: 'installation-live.php'
},
{
id: 'inst-7',
name: 'Zbiornik LPG #5',
location: 'Przemyśl, Baza Południe',
type: 'gas',
typeLabel: 'Zbiornik Gazu',
status: 'online',
statusLabel: 'Online',
lat: 49.7837,
lng: 22.7673,
alert: null,
url: 'installation-live.php'
}
];
const mapEl = document.getElementById('installationsMap');
if (!mapEl || typeof L === 'undefined') {
return;
}
const map = L.map(mapEl).setView([50.45, 22.15], 8);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
maxZoom: 18
}).addTo(map);
const markersById = {};
let closePopupTimer = null;
function createMarkerIcon(hasAlert) {
const pinClass = hasAlert ? ' is-alert' : '';
const iconHtml = hasAlert
? '<i class="bx bx-error"></i>'
: '<i class="bx bx-buildings"></i>';
return L.divIcon({
className: '',
html: '<div class="map-marker-pin' + pinClass + '">' + iconHtml + '</div>',
iconSize: [32, 32],
iconAnchor: [16, 32],
popupAnchor: [0, -28]
});
}
function buildPopupHtml(item) {
const alertBlock = item.alert
? '<div class="alert alert-danger py-2 px-3 mb-2 small">' +
'<i class="bx bx-error-circle me-1"></i>' + item.alert +
'</div>'
: '<p class="text-muted small mb-2">Brak aktywnych alertów</p>';
return (
'<div>' +
'<h6 class="mb-1 fw-bold">' + item.name + '</h6>' +
'<small class="text-muted d-block mb-2"><i class="bx bx-map me-1"></i>' + item.location + '</small>' +
alertBlock +
'<button type="button" class="btn btn-sm btn-primary w-100" onclick="window.location.href=\'' + item.url + '\'">' +
'<i class="bx bx-right-arrow-alt me-1"></i> Przejdź do instalacji' +
'</button>' +
'</div>'
);
}
function statusBadgeClass(status) {
if (status === 'online') return 'bg-success';
if (status === 'alert') return 'bg-danger';
return 'bg-secondary';
}
installations.forEach(function (item) {
const marker = L.marker([item.lat, item.lng], {
icon: createMarkerIcon(Boolean(item.alert)),
title: item.name
});
marker.bindPopup(buildPopupHtml(item));
marker.on('mouseover', function () {
if (closePopupTimer) {
clearTimeout(closePopupTimer);
closePopupTimer = null;
}
marker.openPopup();
});
marker.on('mouseout', function () {
closePopupTimer = setTimeout(function () {
marker.closePopup();
}, 280);
});
marker.on('popupopen', function () {
const popupEl = marker.getPopup().getElement();
if (!popupEl) return;
popupEl.addEventListener('mouseenter', function () {
if (closePopupTimer) {
clearTimeout(closePopupTimer);
closePopupTimer = null;
}
});
popupEl.addEventListener('mouseleave', function () {
closePopupTimer = setTimeout(function () {
marker.closePopup();
}, 200);
});
});
markersById[item.id] = {
item: item,
marker: marker,
visible: true,
matchesFilter: true
};
marker.addTo(map);
});
const listEl = document.getElementById('mapInstallationsList');
const searchInput = document.getElementById('mapSearchInput');
const typeFilter = document.getElementById('mapTypeFilter');
const statusFilter = document.getElementById('mapStatusFilter');
function renderList() {
const fragments = installations.map(function (item) {
const state = markersById[item.id];
if (!state.matchesFilter) {
return '';
}
const hiddenClass = state.visible ? '' : ' is-hidden-on-map';
const alertIcon = item.alert
? '<i class="bx bx-error-circle text-danger ms-1" title="Alert"></i>'
: '';
return (
'<div class="map-list-item border-bottom px-3 py-2' + hiddenClass + '" data-id="' + item.id + '">' +
'<div class="d-flex align-items-start gap-2">' +
'<div class="form-check form-switch mb-0 mt-1">' +
'<input class="form-check-input map-visibility-toggle" type="checkbox" role="switch" ' +
'data-id="' + item.id + '" ' + (state.visible ? 'checked' : '') + ' ' +
'aria-label="Widoczność ' + item.name + '">' +
'</div>' +
'<div class="flex-grow-1 min-w-0">' +
'<div class="d-flex justify-content-between align-items-start gap-2">' +
'<div class="min-w-0">' +
'<div class="fw-semibold text-truncate">' + item.name + alertIcon + '</div>' +
'<small class="text-muted text-truncate d-block">' + item.location + '</small>' +
'</div>' +
'<span class="badge ' + statusBadgeClass(item.status) + '">' + item.statusLabel + '</span>' +
'</div>' +
'<small class="text-muted">' + item.typeLabel + '</small>' +
'</div>' +
'</div>' +
'</div>'
);
}).join('');
listEl.innerHTML = fragments ||
'<div class="text-center text-muted py-4 px-3 small">Brak instalacji spełniających kryteria</div>';
}
function applyFilters() {
const query = (searchInput.value || '').trim().toLowerCase();
const type = typeFilter.value;
const status = statusFilter.value;
installations.forEach(function (item) {
const state = markersById[item.id];
const haystack = (item.name + ' ' + item.location).toLowerCase();
const matchesQuery = !query || haystack.indexOf(query) !== -1;
const matchesType = !type || item.type === type;
const matchesStatus = !status || item.status === status;
state.matchesFilter = matchesQuery && matchesType && matchesStatus;
const shouldShow = state.matchesFilter && state.visible;
if (shouldShow) {
if (!map.hasLayer(state.marker)) {
state.marker.addTo(map);
}
} else if (map.hasLayer(state.marker)) {
map.removeLayer(state.marker);
}
});
renderList();
}
function setAllVisibility(visible) {
installations.forEach(function (item) {
markersById[item.id].visible = visible;
});
applyFilters();
}
function focusInstallation(id) {
const state = markersById[id];
if (!state || !state.matchesFilter) return;
if (!state.visible) {
state.visible = true;
applyFilters();
}
map.flyTo([state.item.lat, state.item.lng], 12, { duration: 0.6 });
state.marker.openPopup();
listEl.querySelectorAll('.map-list-item').forEach(function (el) {
el.classList.toggle('is-focused', el.getAttribute('data-id') === id);
});
}
listEl.addEventListener('change', function (event) {
const toggle = event.target.closest('.map-visibility-toggle');
if (!toggle) return;
const id = toggle.getAttribute('data-id');
markersById[id].visible = toggle.checked;
applyFilters();
});
listEl.addEventListener('click', function (event) {
if (event.target.closest('.map-visibility-toggle')) return;
const row = event.target.closest('.map-list-item');
if (!row) return;
focusInstallation(row.getAttribute('data-id'));
});
searchInput.addEventListener('input', applyFilters);
typeFilter.addEventListener('change', applyFilters);
statusFilter.addEventListener('change', applyFilters);
document.getElementById('showAllInstallationsBtn').addEventListener('click', function () {
setAllVisibility(true);
});
document.getElementById('hideAllInstallationsBtn').addEventListener('click', function () {
setAllVisibility(false);
});
applyFilters();
setTimeout(function () {
map.invalidateSize();
}, 200);
})();
</script>
</body>
</html>
+107
View File
@@ -0,0 +1,107 @@
<?php
$enablePrototypeComments = true;
include '../../header-telemetria.php';
?>
<div class="container-xxl flex-grow-1 container-p-y">
<h4 class="fw-bold py-3 mb-4">
Telemetria <span class="text-muted fw-light">/ Instalacje</span>
</h4>
<div class="card">
<div class="card-header border-bottom d-flex flex-column flex-md-row justify-content-between align-items-md-center">
<input type="text" class="form-control form-control" placeholder="Szukaj instalacji..." style="width: 200px;">
<div class="d-flex flex-column flex-sm-row gap-2 mt-3 mt-md-0">
<select class="form-select form-select" style="width: 150px;">
<option value="">Wszystkie typy</option>
<option value="gas">Zbiornik Gazu</option>
<option value="farm">Kurnik / Farma</option>
</select>
<select class="form-select form-select" style="width: 150px;">
<option value="">Każdy status</option>
<option value="online">Online</option>
<option value="offline">Offline</option>
</select>
<a href="installation-add.php" class="btn btn-primary">
<i class="bx bx-plus-circle me-1"></i> Dodaj nową instalację
</a>
</div>
</div>
<div class="table-responsive text-nowrap">
<table class="table table-hover">
<thead>
<tr>
<th>Instalacja</th>
<th>Typ</th>
<th>Ostatni kontakt</th>
<th>Status</th>
<th>Akcje</th>
</tr>
</thead>
<tbody class="table-border-bottom-0">
<!-- Element 1 -->
<tr>
<td>
<strong>Zbiornik LPG #1</strong><br>
<small class="text-muted">Stalowa Wola, ul. Przemysłowa</small>
</td>
<td><span class="badge bg-label-warning"><i class="bx bxs-flame me-1"></i> Zbiornik Gazu</span></td>
<td>Przed 2 min</td>
<td><span class="badge bg-success">Online</span></td>
<td>
<a href="installation-live.php" class="btn btn-sm btn-icon btn-outline-primary" title="Podgląd Live">
<i class="bx bx-show"></i>
</a>
<a href="installation-edit.php" class="btn btn-sm btn-icon btn-outline-secondary" title="Edycja">
<i class="bx bx-pencil"></i>
</a>
</td>
</tr>
<!-- Element 2 -->
<tr>
<td>
<strong>Kurnik B (Sektor 2)</strong><br>
<small class="text-muted">Farma Wola, Obiekt Główny</small>
</td>
<td><span class="badge bg-label-info"><i class="bx bxs-thermometer me-1"></i> Sterownik Kurnika</span></td>
<td>Przed 1 min</td>
<td><span class="badge bg-success">Online</span></td>
<td>
<a href="installation-live.php" class="btn btn-sm btn-icon btn-outline-primary" title="Podgląd Live">
<i class="bx bx-show"></i>
</a>
<a href="installation-edit.php" class="btn btn-sm btn-icon btn-outline-secondary" title="Edycja">
<i class="bx bx-pencil"></i>
</a>
</td>
</tr>
<!-- Element 3 -->
<tr>
<td>
<strong>Zbiornik LPG #2</strong><br>
<small class="text-muted">Rzeszów, Baza Północ</small>
</td>
<td><span class="badge bg-label-warning"><i class="bx bxs-flame me-1"></i> Zbiornik Gazu</span></td>
<td>3 dni temu</td>
<td><span class="badge bg-danger">Offline</span></td>
<td>
<a href="installation-live.php" class="btn btn-sm btn-icon btn-outline-primary" title="Podgląd Live">
<i class="bx bx-show"></i>
</a>
<a href="installation-edit.php" class="btn btn-sm btn-icon btn-outline-secondary" title="Edycja">
<i class="bx bx-pencil"></i>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="content-backdrop fade"></div>
<?php include '../../footer.php'; ?>
</body>
</html>
+61
View File
@@ -0,0 +1,61 @@
<?php
$enablePrototypeComments = true;
include '../../header-telemetria.php';
?>
<div class="container-xxl flex-grow-1 container-p-y">
<h4 class="fw-bold py-3 mb-4">
Telemetria <span class="text-muted fw-light">/ Logi systemowe</span>
</h4>
<div class="card shadow-sm border-0">
<div class="card-header border-bottom">
<h5 class="card-title mb-0">Dziennik zdarzeń (Logi modułu)</h5>
</div>
<div class="table-responsive text-nowrap">
<table class="table table-hover">
<thead>
<tr>
<th>Data</th>
<th>Użytkownik</th>
<th>Operacja</th>
<th>Szczegóły</th>
</tr>
</thead>
<tbody class="table-border-bottom-0">
<tr>
<td>2026-06-19 16:30</td>
<td><span class="badge bg-label-primary"><i class="bx bx-user me-1"></i> Jan Kowalski</span></td>
<td><span class="fw-bold text-danger">Usunięcie urządzenia</span></td>
<td>ID: 1024</td>
</tr>
<tr>
<td>2026-06-19 16:20</td>
<td><span class="badge bg-label-secondary"><i class="bx bx-cog me-1"></i> System</span></td>
<td><span class="fw-bold text-warning">Zmiana statusu</span></td>
<td>ID: 1024, Status: Online → Offline (utrata łączności)</td>
</tr>
<tr>
<td>2026-06-19 16:15</td>
<td><span class="badge bg-label-primary"><i class="bx bx-user me-1"></i> Jan Kowalski</span></td>
<td><span class="fw-bold text-info">Edycja danych</span></td>
<td>ID: 1024, Edycja danych urządzenia.</td>
</tr>
<tr>
<td>2026-06-19 16:10</td>
<td><span class="badge bg-label-primary"><i class="bx bx-user me-1"></i> Jan Kowalski</span></td>
<td><span class="fw-bold text-success">Dodanie urządzenia</span></td>
<td>MAC: AA:BB:CC:DD:EE:FF, Nazwa: "Sterownik Klimatu Alpha"</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="content-backdrop fade"></div>
<?php include '../../footer.php'; ?>
</body>
</html>
+94
View File
@@ -0,0 +1,94 @@
<?php
$currentPage = basename($_SERVER['PHP_SELF']);
?>
<aside id="layout-menu" class="layout-menu menu-vertical menu bg-menu-theme">
<div class="app-brand demo">
<a href="index.php" class="app-brand-link">
<span class="app-brand-text demo menu-text fw-bold ms-2">telemetria.magico</span>
</a>
<a href="javascript:void(0);" class="layout-menu-toggle menu-link text-large ms-auto">
<i class="bx bx-chevron-left bx-sm align-middle"></i>
</a>
</div>
<div class="menu-inner-shadow"></div>
<ul class="menu-inner py-1">
<li class="menu-header small text-uppercase">
<span class="menu-header-text" data-i18n="Telemetria">Telemetria</span>
</li>
<li class="menu-item <?php echo ($currentPage == 'index.php' || $currentPage == 'dashboard-2.php') ? 'active open' : ''; ?>">
<a href="javascript:void(0);" class="menu-link menu-toggle">
<i class="menu-icon tf-icons bx bx-home-circle"></i>
<div class="text-truncate" data-i18n="Dashboardy">Dashboardy</div>
</a>
<ul class="menu-sub">
<li class="menu-item <?php echo ($currentPage == 'index.php') ? 'active' : ''; ?>">
<a href="index.php" class="menu-link">
<div class="text-truncate" data-i18n="Główny">Główny</div>
</a>
</li>
<li class="menu-item">
<a href="#" class="menu-link">
<div class="text-truncate" data-i18n="Tylko LPG">Tylko LPG</div>
</a>
</li>
<li class="menu-item">
<a href="#" class="menu-link">
<div class="text-truncate" data-i18n="Raport Dzienny">Raport Dzienny</div>
</a>
</li>
</ul>
</li>
<li class="menu-item <?php echo in_array($currentPage, ['installations.php', 'installations-map.php'], true) ? 'active open' : ''; ?>">
<a href="javascript:void(0);" class="menu-link menu-toggle">
<i class="menu-icon tf-icons bx bx-buildings"></i>
<div class="text-truncate" data-i18n="Instalacje">Instalacje</div>
</a>
<ul class="menu-sub">
<li class="menu-item <?php echo ($currentPage == 'installations.php') ? 'active' : ''; ?>">
<a href="installations.php" class="menu-link">
<div class="text-truncate" data-i18n="Lista">Lista instalacji</div>
</a>
</li>
<li class="menu-item <?php echo ($currentPage == 'installations-map.php') ? 'active' : ''; ?>">
<a href="installations-map.php" class="menu-link">
<div class="text-truncate" data-i18n="Mapa instalacji">Mapa instalacji</div>
</a>
</li>
</ul>
</li>
<li class="menu-item <?php echo ($currentPage == 'devices.php') ? 'active' : ''; ?>">
<a href="devices.php" class="menu-link">
<i class="menu-icon tf-icons bx bx-chip"></i>
<div class="text-truncate" data-i18n="Urządzenia">Urządzenia (Sprzęt)</div>
</a>
</li>
<li class="menu-item <?php echo ($currentPage == 'reports.php') ? 'active' : ''; ?>">
<a href="reports.php" class="menu-link">
<i class="menu-icon tf-icons bx bx-bar-chart-alt-2"></i>
<div class="text-truncate" data-i18n="Raporty">Raporty</div>
</a>
</li>
<li class="menu-item <?php echo ($currentPage == 'settings.php' || $currentPage == 'logs.php') ? 'active open' : ''; ?>">
<a href="javascript:void(0);" class="menu-link menu-toggle">
<i class="menu-icon tf-icons bx bx-server"></i>
<div class="text-truncate" data-i18n="System">System</div>
</a>
<ul class="menu-sub">
<li class="menu-item <?php echo ($currentPage == 'settings.php') ? 'active' : ''; ?>">
<a href="settings.php" class="menu-link">
<div class="text-truncate" data-i18n="Ustawienia">Ustawienia</div>
</a>
</li>
<li class="menu-item <?php echo ($currentPage == 'logs.php') ? 'active' : ''; ?>">
<a href="logs.php" class="menu-link">
<div class="text-truncate" data-i18n="Logi">Logi systemowe</div>
</a>
</li>
</ul>
</li>
</ul>
</aside>
+366
View File
@@ -0,0 +1,366 @@
<?php
$enablePrototypeComments = true;
include '../../header-telemetria.php';
?>
<div class="container-xxl flex-grow-1 container-p-y">
<!-- Vendors CSS dla Select2 i Flatpickr -->
<link rel="stylesheet" href="../../assets/vendor/libs/select2/select2.css" />
<link rel="stylesheet" href="../../assets/vendor/libs/flatpickr/flatpickr.css" />
<!-- Nagłówek i przyciski akcji -->
<div
class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-4 gap-3">
<div>
<h4 class="fw-bold mb-1">
<span class="text-muted fw-light">Raporty /</span> Historia zużycia medium
</h4>
<p class="text-muted mb-0">Zestawienie konsumpcji gazu / ubytków poziomu dla wskazanej instalacji we wskazanym okresie.</p>
</div>
<div class="d-flex gap-2">
<button class="btn btn-label-secondary">
<i class="bx bx-export me-1"></i> Eksportuj CSV
</button>
<button class="btn btn-primary">
<i class="bx bx-printer me-1"></i> Drukuj raport
</button>
</div>
</div>
<!-- Karta filtrów -->
<div class="card mb-4">
<div class="card-body">
<form class="row g-3">
<div class="col-12 col-md-3">
<label class="form-label" for="reportInstallation">Instalacja</label>
<select id="reportInstallation" class="form-select select2">
<option value="all">Wszystkie zebrane razem</option>
<option value="1" selected>Zbiornik LPG #1 (Stalowa Wola)</option>
<option value="2">Zbiornik LPG #2 (Rzeszów)</option>
<option value="3">Kurnik Główny</option>
</select>
</div>
<div class="col-12 col-md-2">
<label class="form-label d-block">&nbsp;</label>
<div class="btn-group w-100">
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bx bx-calendar me-1"></i> Zakres
</button>
<ul class="dropdown-menu">
<li><h6 class="dropdown-header text-uppercase">Dni</h6></li>
<li><a class="dropdown-item preset-date-range" href="javascript:void(0);" data-range="today">Dziś</a></li>
<li><a class="dropdown-item preset-date-range" href="javascript:void(0);" data-range="this-week">Ten tydzień</a></li>
<li><a class="dropdown-item preset-date-range" href="javascript:void(0);" data-range="last-week">Poprzedni tydzień</a></li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header text-uppercase">Miesiące</h6></li>
<li><a class="dropdown-item preset-date-range" href="javascript:void(0);" data-range="this-month">Ten miesiąc</a></li>
<li><a class="dropdown-item preset-date-range" href="javascript:void(0);" data-range="last-month">Poprzedni miesiąc</a></li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header text-uppercase">Lata</h6></li>
<li><a class="dropdown-item preset-date-range" href="javascript:void(0);" data-range="this-year">Ten rok</a></li>
<li><a class="dropdown-item preset-date-range" href="javascript:void(0);" data-range="last-year">Poprzedni rok</a></li>
</ul>
</div>
</div>
<div class="col-12 col-md-2">
<label class="form-label" for="reportDateFrom">Data od</label>
<div class="input-group input-group-merge">
<span class="input-group-text"><i class="bx bx-calendar"></i></span>
<input type="text" id="reportDateFrom" class="form-control flatpickr-date"
placeholder="YYYY-MM-DD" value="2026-01-01" />
</div>
</div>
<div class="col-12 col-md-2">
<label class="form-label" for="reportDateTo">Data do</label>
<div class="input-group input-group-merge">
<span class="input-group-text"><i class="bx bx-calendar"></i></span>
<input type="text" id="reportDateTo" class="form-control flatpickr-date"
placeholder="YYYY-MM-DD" value="2026-06-30" />
</div>
</div>
<div class="col-12 col-md-2">
<label class="form-label" for="reportGranularity">Szczegółowość</label>
<select id="reportGranularity" class="form-select select2">
<option value="days">Dni</option>
<option value="weeks">Tygodnie</option>
<option value="months" selected>Miesiące</option>
</select>
<small id="daysLimitInfo" class="text-muted d-none mt-1 d-block">
<i class="bx bx-info-circle fs-6 align-middle me-1"></i>Max 366 dni.
</small>
</div>
<div class="col-12 col-md-1 d-flex align-items-end">
<button type="button" class="btn btn-primary w-100 w-md-auto px-3" title="Generuj raport">
<i class="bx bx-refresh"></i>
</button>
</div>
</form>
</div>
</div>
<!-- Karta z wykresem -->
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title m-0">Zużycie medium w czasie (litry)</h5>
</div>
<div class="card-body">
<div id="consumptionReportChart" style="min-height: 350px;"></div>
</div>
</div>
<!-- Karta z tabelą danych -->
<div class="card">
<div class="card-header border-bottom">
<h5 class="card-title m-0">Szczegółowe zestawienie</h5>
</div>
<div class="table-responsive text-nowrap">
<table class="table table-hover">
<thead class="table-light">
<tr>
<th>Okres</th>
<th class="text-center">Liczba odczytów</th>
<th class="text-end">Stan początkowy</th>
<th class="text-end">Stan końcowy</th>
<th class="text-end">Całkowite zużycie</th>
</tr>
</thead>
<tbody>
<tr>
<td class="fw-medium">Styczeń 2026</td>
<td class="text-center">744</td>
<td class="text-end">2100 l (77%)</td>
<td class="text-end">1550 l (57%)</td>
<td class="text-end fw-bold text-danger">-550 l</td>
</tr>
<tr>
<td class="fw-medium">Luty 2026</td>
<td class="text-center">672</td>
<td class="text-end">1550 l (57%)</td>
<td class="text-end">850 l (31%)</td>
<td class="text-end fw-bold text-danger">-700 l</td>
</tr>
<tr class="table-primary bg-label-primary">
<td class="fw-medium">Marzec 2026 <i class="bx bx-gas-pump ms-1 text-primary" title="Zarejestrowano tankowanie"></i></td>
<td class="text-center">744</td>
<td class="text-end">850 l (31%)</td>
<td class="text-end">2500 l (92%)</td>
<td class="text-end fw-bold text-success">+1650 l</td>
</tr>
<tr>
<td class="fw-medium">Kwiecień 2026</td>
<td class="text-center">720</td>
<td class="text-end">2500 l (92%)</td>
<td class="text-end">2100 l (77%)</td>
<td class="text-end fw-bold text-danger">-400 l</td>
</tr>
<tr>
<td class="fw-medium">Maj 2026</td>
<td class="text-center">744</td>
<td class="text-end">2100 l (77%)</td>
<td class="text-end">1800 l (66%)</td>
<td class="text-end fw-bold text-danger">-300 l</td>
</tr>
<tr>
<td class="fw-medium">Czerwiec 2026</td>
<td class="text-center">360</td>
<td class="text-end">1800 l (66%)</td>
<td class="text-end">1620 l (60%)</td>
<td class="text-end fw-bold text-danger">-180 l</td>
</tr>
</tbody>
<tfoot class="table-light fw-bold">
<tr>
<td>Podsumowanie</td>
<td class="text-center">3984</td>
<td class="text-end text-muted">-</td>
<td class="text-end text-muted">-</td>
<td class="text-end text-primary fs-5">Zbilansowano: -480 l</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<?php include '../../footer.php'; ?>
<!-- Vendors JS -->
<script src="../../assets/vendor/libs/select2/select2.js"></script>
<script src="../../assets/vendor/libs/flatpickr/flatpickr.js"></script>
<!-- ApexCharts scripts and local chart init -->
<script src="../../assets/vendor/libs/apex-charts/apexcharts.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
// 1. Inicjalizacja Flatpickr dla zakresu dat
if (typeof flatpickr !== 'undefined') {
const dateInputs = document.querySelectorAll('.flatpickr-date');
let fpInstances = [];
dateInputs.forEach(el => {
fpInstances.push(flatpickr(el, {
dateFormat: 'Y-m-d',
locale: 'pl'
}));
});
if (fpInstances.length === 2 && typeof $ !== 'undefined') {
const fpFrom = fpInstances[0];
const fpTo = fpInstances[1];
document.querySelectorAll('.preset-date-range').forEach(item => {
item.addEventListener('click', function (e) {
e.preventDefault();
const rangeType = this.getAttribute('data-range');
const today = new Date();
let startDate = new Date();
let endDate = new Date();
switch (rangeType) {
case 'today':
break;
case 'this-week':
const day = today.getDay() || 7;
startDate.setDate(today.getDate() - day + 1);
break;
case 'last-week':
const lastWeek = new Date(today);
lastWeek.setDate(today.getDate() - 7);
const dayLW = lastWeek.getDay() || 7;
startDate = new Date(lastWeek);
startDate.setDate(lastWeek.getDate() - dayLW + 1);
endDate = new Date(lastWeek);
endDate.setDate(lastWeek.getDate() - dayLW + 7);
break;
case 'this-month':
startDate = new Date(today.getFullYear(), today.getMonth(), 1);
endDate = new Date(today.getFullYear(), today.getMonth() + 1, 0);
break;
case 'last-month':
startDate = new Date(today.getFullYear(), today.getMonth() - 1, 1);
endDate = new Date(today.getFullYear(), today.getMonth(), 0);
break;
case 'this-year':
startDate = new Date(today.getFullYear(), 0, 1);
endDate = new Date(today.getFullYear(), 11, 31);
break;
case 'last-year':
startDate = new Date(today.getFullYear() - 1, 0, 1);
endDate = new Date(today.getFullYear() - 1, 11, 31);
break;
}
if (startDate && endDate) {
fpFrom.setDate(startDate);
fpTo.setDate(endDate);
}
});
});
}
}
// 2. Inicjalizacja Select2
if (typeof $ !== 'undefined' && $('.select2').length) {
$('.select2').select2({
minimumResultsForSearch: 5
});
// Obsługa pojawiania się podpowiedzi po wybraniu z selecta
$('#reportGranularity').on('change', function () {
const daysInfo = document.getElementById('daysLimitInfo');
if (this.value === 'days') {
daysInfo.classList.remove('d-none');
} else {
daysInfo.classList.add('d-none');
}
});
}
// 3. Inicjalizacja Wykresu
const chartEl = document.querySelector('#consumptionReportChart');
if (chartEl && typeof ApexCharts !== 'undefined') {
const chartConfig = {
chart: {
height: 350,
type: 'area', // Zmieniono na area, by ładnie pokazywać spadek/wzrost litrów
parentHeightOffset: 0,
toolbar: {
show: false
}
},
dataLabels: {
enabled: false
},
stroke: {
curve: 'smooth',
width: 3
},
series: [{
name: 'Stan początkowy w danym miesiącu (litry)',
data: [2100, 1550, 850, 2500, 2100, 1800]
}],
xaxis: {
categories: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec'],
axisBorder: {
show: false
},
axisTicks: {
show: false
},
labels: {
style: {
colors: '#a1acb8',
fontSize: '13px'
}
}
},
yaxis: {
labels: {
formatter: function (val) {
return val.toLocaleString('pl-PL') + " l";
},
style: {
colors: '#a1acb8',
fontSize: '13px'
}
}
},
colors: ['#00cfdd'], // Kolor np. cyan
fill: {
type: 'gradient',
gradient: {
shadeIntensity: 1,
opacityFrom: 0.5,
opacityTo: 0.1,
stops: [0, 90, 100]
}
},
grid: {
borderColor: '#e9ecef',
strokeDashArray: 4,
padding: {
top: -20,
bottom: -10,
left: 20,
right: 20
}
},
tooltip: {
y: {
formatter: function (val) {
return val.toLocaleString('pl-PL') + " l";
}
}
}
};
const consumptionChart = new ApexCharts(chartEl, chartConfig);
consumptionChart.render();
}
});
</script>
</body>
</html>
+134
View File
@@ -0,0 +1,134 @@
<?php
$enablePrototypeComments = true;
include '../../header-telemetria.php';
?>
<div class="container-xxl flex-grow-1 container-p-y">
<!-- Header with description -->
<div class="card p-0 mb-4">
<div class="card-body d-flex flex-column flex-md-row justify-content-between p-0">
<!-- Left image -->
<div class="d-none d-md-flex app-academy-md-25 card-body py-0 pt-3 ps-5 align-items-center">
<img src="../../assets/img/illustrations/bulb-light.png" width="90" class="img-fluid" alt="Bulb"
data-app-light-img="illustrations/bulb-light.png" data-app-dark-img="illustrations/bulb-dark.png">
</div>
<!-- Center context -->
<div class="app-academy-md-50 card-body d-flex align-items-md-center flex-column text-md-center mb-1 py-5">
<h4 class="card-title mb-4 px-md-12 lh-base fw-bold">
Analizuj dane z instalacji<br>
w <span class="text-primary text-nowrap">przejrzystych raportach</span>.
</h4>
<p class="mb-4 col-md-10 text-muted">
Poniżej prezentujemy dostępne raporty telemetryczne. Wybierz zestawienie, aby
przeanalizować historię zużycia medium, zidentyfikować anomalie oraz zweryfikować
stabilność połączeń na Twoich obiektach.
</p>
</div>
<!-- Right image -->
<div class="d-none d-md-flex app-academy-md-25 align-items-end justify-content-end pe-4 pb-2">
<img src="../../assets/img/illustrations/pencil-rocket.png" alt="pencil rocket" height="150"
class="scaleX-n1-rtl">
</div>
</div>
</div>
<!-- Reports Gallery -->
<div class="card p-4">
<div class="row">
<!-- Raport historii zużycia -->
<div class="col-sm-6 col-lg-4 mb-4">
<div class="card p-2 h-100 shadow-none border border-primary">
<div class="card-body p-4 pt-2 d-flex flex-column">
<div class="d-flex justify-content-between align-items-center mb-4">
<span class="badge bg-label-primary">Eksploatacja</span>
</div>
<a href="report-consumption.php" class="h5 mb-2 fw-semibold text-primary">Historia zużycia medium</a>
<p class="mt-1 text-muted mb-4 flex-grow-1">
Wykres i tabela prezentująca ubytki / konsumpcję gazu (lub innego medium) dla wskazanej instalacji w danym przedziale czasowym. Oblicza dzienne i miesięczne zużycie.
</p>
<div class="d-flex flex-column flex-md-row gap-4 mt-auto">
<a class="w-100 btn btn-primary d-flex align-items-center justify-content-center"
href="report-consumption.php">
<span class="me-2">Wygeneruj</span>
<i class="bx bx-chevron-right icon-sm lh-1 scaleX-n1-rtl"></i>
</a>
</div>
</div>
</div>
</div>
<!-- Dziennik alertów i przekroczeń -->
<div class="col-sm-6 col-lg-4 mb-4">
<div class="card p-2 h-100 shadow-none border">
<div class="card-body p-4 pt-2 d-flex flex-column">
<div class="d-flex justify-content-between align-items-center mb-4">
<span class="badge bg-label-danger">Bezpieczeństwo</span>
</div>
<a href="javascript:void(0);" class="h5 mb-2 fw-semibold">Dziennik alertów</a>
<p class="mt-1 text-muted mb-4 flex-grow-1">
Ewidencja wszystkich wygenerowanych alarmów, w tym przekroczeń ciśnienia, krytycznie niskich stanów, z datami ich wystąpienia i powiadomionymi osobami.
</p>
<div class="d-flex flex-column flex-md-row gap-4 mt-auto">
<a class="w-100 btn btn-label-primary d-flex align-items-center justify-content-center"
href="javascript:void(0);">
<span class="me-2">Wygeneruj</span>
<i class="bx bx-chevron-right icon-sm lh-1 scaleX-n1-rtl"></i>
</a>
</div>
</div>
</div>
</div>
<!-- Analiza stabilności infrastruktury -->
<div class="col-sm-6 col-lg-4 mb-4">
<div class="card p-2 h-100 shadow-none border">
<div class="card-body p-4 pt-2 d-flex flex-column">
<div class="d-flex justify-content-between align-items-center mb-4">
<span class="badge bg-label-warning">Serwis i Sprzęt</span>
</div>
<a href="javascript:void(0);" class="h5 mb-2 fw-semibold">Analiza stabilności sieci</a>
<p class="mt-1 text-muted mb-4 flex-grow-1">
Monitorowanie przerw w łączności GSM, skoków napięcia i krzywej rozładowania baterii. Służy do planowania prac serwisowych (Predictive Maintenance).
</p>
<div class="d-flex flex-column flex-md-row gap-4 mt-auto">
<a class="w-100 btn btn-label-primary d-flex align-items-center justify-content-center"
href="javascript:void(0);">
<span class="me-2">Wygeneruj</span>
<i class="bx bx-chevron-right icon-sm lh-1 scaleX-n1-rtl"></i>
</a>
</div>
</div>
</div>
</div>
<!-- Sumaryczny bilans instalacji -->
<div class="col-sm-6 col-lg-4 mb-4">
<div class="card p-2 h-100 shadow-none border">
<div class="card-body p-4 pt-2 d-flex flex-column">
<div class="d-flex justify-content-between align-items-center mb-4">
<span class="badge bg-label-info">Podsumowanie całkowite</span>
</div>
<a href="javascript:void(0);" class="h5 mb-2 fw-semibold">Bilans flotowy</a>
<p class="mt-1 text-muted mb-4 flex-grow-1">
Całkowite zestawienie posiadanych zasobów na dany dzień sumuje dostępne litry gazu we wszystkich połączonych zbiornikach, ułatwiając logistykę tankowania.
</p>
<div class="d-flex flex-column flex-md-row gap-4 mt-auto">
<a class="w-100 btn btn-label-primary d-flex align-items-center justify-content-center"
href="javascript:void(0);">
<span class="me-2">Wygeneruj</span>
<i class="bx bx-chevron-right icon-sm lh-1 scaleX-n1-rtl"></i>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<?php include '../../footer.php'; ?>
+90
View File
@@ -0,0 +1,90 @@
<?php
$enablePrototypeComments = true;
include '../../header-telemetria.php';
?>
<div class="container-xxl flex-grow-1 container-p-y">
<h4 class="fw-bold py-3 mb-4">
Telemetria <span class="text-muted fw-light">/ Ustawienia</span>
</h4>
<div class="row">
<div class="col-md-6 col-12 mb-4">
<div class="card shadow-sm border-0">
<div class="card-header border-bottom">
<h5 class="card-title mb-0">Powiadomienia i Alerty</h5>
</div>
<div class="card-body pt-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h6 class="mb-1">Aktywny moduł alertów</h6>
<small class="text-muted">Pozwala na ustawianie zaawansowanych wyzwalaczy i powiadomień SMS/Email po przekroczeniu progów alarmowych w instalacjach.</small>
</div>
<div class="form-check form-switch form-switch-lg ms-3 mb-0">
<input class="form-check-input" type="checkbox" id="alertsModuleSwitch">
</div>
</div>
<button type="button" class="btn btn-primary mt-3" onclick="saveSettings()">Zapisz ustawienia</button>
</div>
</div>
</div>
</div>
</div>
<!-- Modal potwierdzenia włączenia -->
<div class="modal fade" id="confirmPaymentModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-warning border-2">
<div class="modal-header">
<h5 class="modal-title text-warning"><i class="bx bx-error-circle me-1"></i> Potwierdzenie subskrypcji</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" onclick="cancelSwitch()"></button>
</div>
<div class="modal-body">
<p>Uruchomienie modułu <strong>Powiadomienia i Alerty</strong> wiąże się z dodatkową opłatą abonamentową w wysokości <strong>50 zł / msc</strong> doliczaną do Twojej faktury.</p>
<p class="mb-0 text-dark fw-bold">Czy na pewno chcesz włączyć ten moduł?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal" onclick="cancelSwitch()">Anuluj</button>
<button type="button" class="btn btn-success" data-bs-dismiss="modal" onclick="confirmSave()">Tak, włączam moduł</button>
</div>
</div>
</div>
</div>
<script>
let isCurrentlyEnabled = false; // Zakładamy że na start jest wyłączone
function saveSettings() {
const switchEl = document.getElementById('alertsModuleSwitch');
// Jeśli chcemy włączyć, a wcześniej było wyłączone
if (switchEl.checked && !isCurrentlyEnabled) {
var paymentModal = new bootstrap.Modal(document.getElementById('confirmPaymentModal'));
paymentModal.show();
} else if (!switchEl.checked && isCurrentlyEnabled) {
alert('Moduł został pomyślnie wyłączony. Opłata nie będzie naliczana od następnego okresu rozliczeniowego.');
isCurrentlyEnabled = false;
} else {
alert('Ustawienia zapisane bez zmian.');
}
}
function confirmSave() {
isCurrentlyEnabled = true;
document.getElementById('alertsModuleSwitch').checked = true;
alert('Moduł został włączony! Doliczono 50 zł/msc do Twojego abonamentu. Możesz teraz konfigurować alerty w instalacjach.');
}
function cancelSwitch() {
// Jeśli anulowano modal, przywracamy przełącznik do stanu wylączonego
document.getElementById('alertsModuleSwitch').checked = false;
}
</script>
<div class="content-backdrop fade"></div>
<?php include '../../footer.php'; ?>
</body>
</html>
+323 -17
View File
@@ -109,15 +109,33 @@ include '../../header-travel.php';
</div>
<div class="card-body p-3">
<div class="row g-3">
<div class="col-md-3">
<div class="col-md-2">
<label class="form-label small mb-1">Dostawca / NIP</label>
<input type="text" class="form-control" placeholder="Szukaj...">
</div>
<div class="col-md-3">
<div class="col-md-2">
<label class="form-label small mb-1">Numer faktury</label>
<input type="text" class="form-control" placeholder="Wpisz numer">
</div>
<div class="col-md-3">
<label class="form-label small mb-1">Rodzaj faktury</label>
<select class="form-select">
<option value="">Wszystkie</option>
<option value="podstawowa">Podstawowa</option>
<option value="zaliczkowa">Zaliczkowa</option>
<option value="rozliczeniowa">Rozliczeniowa</option>
<option value="uproszczona">Uproszczona</option>
<option value="korygujaca">Korygująca</option>
<option value="korygujaca_zaliczkowa">Korygująca zaliczkową</option>
<option value="korygujaca_rozliczeniowa">Korygująca rozliczeniową</option>
<option value="podstawowa_pef">Podstawowa PEF</option>
<option value="specjalizowana_pef">Specjalizowana PEF</option>
<option value="korygujaca_pef">Korygująca PEF</option>
<option value="pierwotna_rr">Pierwotna RR</option>
<option value="korygujaca_rr">Korygująca RR</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label small mb-1">Status powiązania</label>
<select class="form-select">
<option value="">Wszystkie</option>
@@ -511,6 +529,18 @@ include '../../header-travel.php';
<div class="tab-pane fade" id="navs-faktury" role="tabpanel">
<div class="card shadow-sm">
<div class="card-header border-bottom d-flex justify-content-between align-items-center py-3">
<h5 class="card-title mb-0">Baza faktur</h5>
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="bulkOperationsDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bx bx-check-double me-1"></i> Operacje masowe
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="bulkOperationsDropdown">
<h6 class="dropdown-header text-uppercase ps-3">Finanse</h6>
<li><a class="dropdown-item" href="javascript:void(0);" data-bs-toggle="modal" data-bs-target="#pekaoConnectModal"><i class="bx bx-transfer-alt me-2"></i> Prześlij do PeKaO Connect</a></li>
</ul>
</div>
</div>
<div class="table-responsive text-nowrap">
<table class="table table-hover">
<thead class="table-light">
@@ -534,7 +564,7 @@ include '../../header-travel.php';
<i class="bx bxs-file-pdf text-danger fs-4 me-2"></i>
<div class="d-flex flex-column">
<span class="fw-semibold text-dark">FV/2026/05/1234</span>
<small class="text-muted">Faktura VAT</small>
<small class="text-muted">Podstawowa</small>
</div>
</div>
</td>
@@ -558,7 +588,7 @@ include '../../header-travel.php';
</div>
</td>
<td class="text-end">
<button class="btn btn-sm btn-icon btn-text-secondary" data-bs-toggle="tooltip"
<button class="btn btn-sm btn-icon btn-text-secondary" data-bs-toggle="modal" data-bs-target="#invoicePreviewModal" data-bs-toggle="tooltip"
title="Podgląd dokumentu"><i class="bx bx-show"></i></button>
<button class="btn btn-sm btn-primary ms-1" data-bs-toggle="modal"
data-bs-target="#modalLinkExistingInvoice">Połącz ze zleceniem</button>
@@ -572,7 +602,7 @@ include '../../header-travel.php';
<i class="bx bx-receipt text-success fs-4 me-2"></i>
<div class="d-flex flex-column">
<span class="fw-semibold text-dark">F-2026-999</span>
<small class="text-muted">Faktura VAT z KSeF</small>
<small class="text-muted">Podstawowa</small>
</div>
</div>
</td>
@@ -599,7 +629,7 @@ include '../../header-travel.php';
<td class="text-end">
<button class="btn btn-sm btn-icon btn-text-secondary" data-bs-toggle="tooltip"
title="Szczegóły rozksięgowania"><i class="bx bx-list-ul"></i></button>
<button class="btn btn-sm btn-icon btn-text-secondary"><i
<button class="btn btn-sm btn-icon btn-text-secondary" data-bs-toggle="modal" data-bs-target="#invoicePreviewModal"><i
class="bx bx-show"></i></button>
</td>
</tr>
@@ -612,7 +642,7 @@ include '../../header-travel.php';
title="Skan z aplikacji pilota"></i>
<div class="d-flex flex-column">
<span class="fw-semibold text-dark">Paragon/FV 12</span>
<small class="text-muted">Faktura zaliczkowa</small>
<small class="text-muted">Zaliczkowa</small>
</div>
</div>
</td>
@@ -639,7 +669,7 @@ include '../../header-travel.php';
</div>
</td>
<td class="text-end">
<button class="btn btn-sm btn-icon btn-text-secondary" data-bs-toggle="tooltip"
<button class="btn btn-sm btn-icon btn-text-secondary" data-bs-toggle="modal" data-bs-target="#invoicePreviewModal" data-bs-toggle="tooltip"
title="Podgląd skanu"><i class="bx bx-show"></i></button>
</td>
</tr>
@@ -675,7 +705,7 @@ include '../../header-travel.php';
</div>
</td>
<td class="text-end">
<button class="btn btn-sm btn-icon btn-text-secondary" data-bs-toggle="tooltip"
<button class="btn btn-sm btn-icon btn-text-secondary" data-bs-toggle="modal" data-bs-target="#invoicePreviewModal" data-bs-toggle="tooltip"
title="Podgląd dokumentu"><i class="bx bx-show"></i></button>
<button class="btn btn-sm btn-primary ms-1" data-bs-toggle="modal"
data-bs-target="#modalLinkExistingInvoice">Połącz ze zleceniem</button>
@@ -689,7 +719,7 @@ include '../../header-travel.php';
<i class="bx bx-receipt text-primary fs-4 me-2"></i>
<div class="d-flex flex-column">
<span class="fw-semibold text-dark">FVZ/2026/04/311</span>
<small class="text-muted">Faktura zaliczkowa</small>
<small class="text-muted">Zaliczkowa</small>
</div>
</div>
</td>
@@ -713,7 +743,7 @@ include '../../header-travel.php';
</div>
</td>
<td class="text-end">
<button class="btn btn-sm btn-icon btn-text-secondary" data-bs-toggle="tooltip"
<button class="btn btn-sm btn-icon btn-text-secondary" data-bs-toggle="modal" data-bs-target="#invoicePreviewModal" data-bs-toggle="tooltip"
title="Podgląd dokumentu"><i class="bx bx-show"></i></button>
</td>
</tr>
@@ -725,7 +755,7 @@ include '../../header-travel.php';
<i class="bx bxs-file-pdf text-info fs-4 me-2"></i>
<div class="d-flex flex-column">
<span class="fw-semibold text-dark">KSEF/2026/05/0042</span>
<small class="text-muted">Faktura VAT z KSeF</small>
<small class="text-muted">Podstawowa</small>
</div>
</div>
</td>
@@ -752,7 +782,7 @@ include '../../header-travel.php';
</div>
</td>
<td class="text-end">
<button class="btn btn-sm btn-icon btn-text-secondary" data-bs-toggle="tooltip"
<button class="btn btn-sm btn-icon btn-text-secondary" data-bs-toggle="modal" data-bs-target="#invoicePreviewModal" data-bs-toggle="tooltip"
title="Podgląd dokumentu"><i class="bx bx-show"></i></button>
<button class="btn btn-sm btn-primary ms-1" data-bs-toggle="modal"
data-bs-target="#modalLinkExistingInvoice">Połącz ze zleceniem</button>
@@ -793,7 +823,7 @@ include '../../header-travel.php';
<td class="text-end">
<button class="btn btn-sm btn-icon btn-text-secondary" data-bs-toggle="tooltip"
title="Szczegóły rozksięgowania"><i class="bx bx-list-ul"></i></button>
<button class="btn btn-sm btn-icon btn-text-secondary"><i
<button class="btn btn-sm btn-icon btn-text-secondary" data-bs-toggle="modal" data-bs-target="#invoicePreviewModal"><i
class="bx bx-show"></i></button>
</td>
</tr>
@@ -811,6 +841,265 @@ include '../../header-travel.php';
</div>
<!-- Modal: PeKaO Connect Bulk Transfer -->
<div class="modal fade" id="pekaoConnectModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header border-bottom py-3">
<h5 class="modal-title" id="pekaoConnectModalTitle"><i class="bx bx-transfer-alt text-primary me-2"></i> Przesyłanie do PeKaO Connect</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-4">
<div class="alert alert-info d-flex" role="alert">
<span class="badge badge-center rounded-pill bg-info border-label-info p-3 me-3"><i class="bx bx-info-circle fs-5"></i></span>
<div class="d-flex flex-column ps-1">
<h6 class="alert-heading d-flex align-items-center mb-1">Wymagana autoryzacja w banku</h6>
<span>Zaznaczone faktury zostaną przesłane i dodane do koszyka w systemie PeKaO Connect. Aby zrealizować płatności, musisz następnie zalogować się do bankowości elektronicznej i zautoryzować przygotowaną paczkę przelewów.</span>
</div>
</div>
<div class="card bg-lighter shadow-none border border-dashed mb-0">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted">Wybranych faktur:</span>
<span class="fw-bold">4</span>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted">Łączna kwota paczki:</span>
<span class="fw-bold fs-5 text-primary">12 450,00 PLN</span>
</div>
</div>
</div>
</div>
<div class="modal-footer border-top py-3">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Anuluj</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal"><i class="bx bx-send me-1"></i> Utwórz paczkę</button>
</div>
</div>
</div>
</div>
<!-- Modal: Invoice Preview -->
<div class="modal fade" id="invoicePreviewModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header border-bottom py-3 d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center gap-3">
<h4 class="modal-title fw-bold mb-0">FV/2026/02/001</h4>
<span class="badge bg-label-success fs-6">Opłacona</span>
<span class="badge bg-label-secondary fs-6">Podstawowa</span>
</div>
<div class="d-flex align-items-center gap-2">
<button type="button" class="btn btn-icon btn-label-secondary" data-bs-toggle="tooltip"
title="Drukuj PDF">
<i class="bx bx-printer"></i>
</button>
<button type="button" class="btn btn-outline-primary d-flex align-items-center">
<i class="bx bx-link-external me-1"></i> Otwórz w invoice.travel
</button>
<div class="dropdown">
<button class="btn btn-primary" type="button" id="dropdownInvoiceActions"
data-bs-toggle="dropdown" aria-expanded="false">
Więcej akcji <i class="bx bx-chevron-down ms-1"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="dropdownInvoiceActions">
<li><button class="dropdown-item"><i class="bx bx-edit-alt me-2"></i> Edytuj</button></li>
<div class="dropdown-divider"></div>
<li><button class="dropdown-item text-danger"><i class="bx bx-trash me-2"></i> Usuń fakturę
</button></li>
</ul>
</div>
<button type="button" class="btn-close ms-2" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body bg-lighter p-4">
<div class="row g-4 mb-4">
<div class="col-lg-4">
<div class="card h-100 shadow-sm border-0">
<div class="card-body p-4">
<h6 class="card-title m-0 mb-3"><i class="bx bx-store me-2"></i>Sprzedawca</h6>
<h6 class="mb-1">OVH Sp. z o.o.</h6>
<p class="mb-1 mt-2">ul. Swobodna 1</p>
<p class="mb-2">50-088 Wrocław, Polska</p>
<span class="badge bg-label-secondary">NIP: 899 25 20 556</span>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card h-100 shadow-sm border-0">
<div class="card-body p-4">
<h6 class="card-title m-0 mb-3"><i class="bx bx-user me-2"></i>Nabywca</h6>
<h6 class="mb-1">MARIUSZ BANACZYK "BURGER BAR"</h6>
<p class="mb-1 text-muted small">KARCZMA BIESIADA, KULTOWA ZAPIEKANKA</p>
<p class="mb-1 mt-2">ul. Ofiar Katynia 2</p>
<p class="mb-2">37-450 Stalowa Wola, Polska</p>
<span class="badge bg-label-secondary">NIP: 865 000 14 51</span>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card h-100 shadow-sm border-0 bg-white">
<div class="card-body p-4">
<h6 class="card-title m-0 mb-3"><i class="bx bx-info-circle me-2"></i>Szczegóły</h6>
<table class="table table-borderless table-sm m-0 px-0">
<tbody>
<tr>
<td class="text-muted ps-0 py-1">Rodzaj faktury:</td>
<td class="fw-medium text-end pe-0 py-1">Podstawowa</td>
</tr>
<tr>
<td class="text-muted ps-0 py-1">Data zakupu:</td>
<td class="fw-medium text-end pe-0 py-1">2026-02-15</td>
</tr>
<tr>
<td class="text-muted ps-0 py-1">Termin płatności:</td>
<td class="fw-bold text-end pe-0 py-1">2026-02-22</td>
</tr>
<tr>
<td class="text-muted ps-0 py-1">Kategoria kosztu:</td>
<td class="fw-medium text-end pe-0 py-1">Usługi obce</td>
</tr>
<tr>
<td class="text-muted ps-0 py-1">Metoda:</td>
<td class="fw-medium text-end pe-0 py-1">Przelew</td>
</tr>
<tr>
<td class="text-muted ps-0 py-1">Waluta / Język:</td>
<td class="fw-medium text-end pe-0 py-1">PLN / Polski</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="card shadow-sm border-0 mb-4">
<div class="table-responsive text-nowrap">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th style="width: 50px;">Lp</th>
<th>Towar / Usługa</th>
<th>J.m.</th>
<th class="text-center">Ilość</th>
<th class="text-end">Cena netto</th>
<th class="text-end">Cena brutto</th>
<th class="text-center">VAT</th>
<th class="text-end">Wartość netto</th>
<th class="text-end">Wartość brutto</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-muted">1</td>
<td>
<div class="d-flex flex-column">
<span class="fw-medium">Hosting i domeny</span>
<small class="text-muted">Kod: -</small>
</div>
</td>
<td>szt.</td>
<td class="text-center">1</td>
<td class="text-end">100,00 zł</td>
<td class="text-end">123,00 zł</td>
<td class="text-center"><span class="badge bg-label-secondary">23%</span></td>
<td class="text-end fw-medium">100,00 zł</td>
<td class="text-end fw-bold text-heading">123,00 zł</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row mb-4 g-4">
<div class="col-md-7">
<div class="card shadow-sm border-0 h-100 bg-white p-4">
<h6 class="text-uppercase text-muted fw-bold mb-3 border-bottom pb-2">Dane do płatności</h6>
<div class="mb-4">
<p class="mb-1 text-body"><strong class="text-muted fw-medium me-2">Bank:</strong> mBank S.A.</p>
<p class="mb-0 text-body d-flex align-items-center">
<strong class="text-muted fw-medium me-2">Rachunek:</strong>
<span class="font-monospace">34 1140 2004 0000 3002 0100 5005</span>
<button class="btn btn-sm btn-icon text-secondary ms-2 p-0" data-bs-toggle="tooltip" title="Kopiuj rachunek"><i class="bx bx-copy"></i></button>
</p>
</div>
<h6 class="text-uppercase text-muted fw-bold mb-3 border-bottom pb-2">Opis zdarzenia</h6>
<p class="mb-0 text-body">
Opłata roczna za usługi hostingowe dla domeny
</p>
</div>
</div>
<div class="col-md-5">
<div class="card shadow-sm border-0 h-100 bg-white p-4">
<h6 class="text-uppercase text-muted fw-bold mb-3 border-bottom pb-2">Suma</h6>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">Wartość netto:</span>
<span class="fw-medium">100,00 zł</span>
</div>
<div class="d-flex justify-content-between mb-3 border-bottom pb-3">
<span class="text-muted">Wartość VAT:</span>
<span class="fw-medium">23,00 zł</span>
</div>
<div class="d-flex justify-content-between align-items-center mb-3">
<span class="fs-5 text-body">Wartość brutto:</span>
<span class="fs-4 fw-bold text-primary">123,00 zł</span>
</div>
<div class="d-flex justify-content-between text-success">
<span>Zapłacono:</span>
<span class="fw-bold">123,00 zł</span>
</div>
<div class="d-flex justify-content-between text-muted mt-1 small">
<span>Pozostało do zapłaty:</span>
<span>0,00 zł</span>
</div>
</div>
</div>
</div>
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white border-bottom py-3">
<h6 class="card-title m-0"><i class="bx bx-paperclip me-2"></i>Załączniki</h6>
</div>
<div class="card-body p-4">
<div class="d-flex flex-wrap gap-3">
<div
class="border rounded p-3 d-flex align-items-center justify-content-between w-100 mb-2">
<div class="d-flex align-items-center">
<i class="bx bxs-file-pdf fs-2 text-danger me-3"></i>
<div>
<h6 class="mb-0">FZ2026_02_001.pdf</h6>
<small class="text-muted">1.2 MB &bull; Wgrano: 2026-02-15 10:23</small>
</div>
</div>
<div class="d-flex gap-2">
<button class="btn btn-icon btn-sm btn-label-secondary"><i
class="bx bx-show"></i></button>
<button class="btn btn-icon btn-sm btn-label-primary"><i
class="bx bx-download"></i></button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="modalLinkedInvoicePreview" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content border-0 shadow-lg">
@@ -824,7 +1113,7 @@ include '../../header-travel.php';
<div class="row g-3">
<div class="col-md-6">
<div class="small text-muted">Numer faktury</div>
<div class="fw-semibold">FV/2026/05/1234</div>
<div class="fw-semibold d-flex align-items-center gap-2">FV/2026/05/1234 <span class="badge bg-label-secondary" style="font-size: 0.7rem;">Podstawowa</span></div>
</div>
<div class="col-md-6">
<div class="small text-muted">Dostawca</div>
@@ -1101,13 +1390,30 @@ include '../../header-travel.php';
</div>
<div class="card-body pt-3 pb-2">
<div class="row g-2">
<div class="col-sm-6 mb-2">
<div class="col-sm-4 mb-2">
<label class="form-label small mb-1">Nr faktury <span
class="text-danger">*</span></label>
<input type="text" class="form-control form-control-sm"
value="FV/2026/05/123">
</div>
<div class="col-sm-6 mb-2">
<div class="col-sm-4 mb-2">
<label class="form-label small mb-1">Rodzaj faktury</label>
<select class="form-select form-select-sm">
<option value="podstawowa" selected>Podstawowa</option>
<option value="zaliczkowa">Zaliczkowa</option>
<option value="rozliczeniowa">Rozliczeniowa</option>
<option value="uproszczona">Uproszczona</option>
<option value="korygujaca">Korygująca</option>
<option value="korygujaca_zaliczkowa">Koryg. zaliczkową</option>
<option value="korygujaca_rozliczeniowa">Koryg. rozliczeniową</option>
<option value="podstawowa_pef">Podstawowa PEF</option>
<option value="specjalizowana_pef">Specjalizowana PEF</option>
<option value="korygujaca_pef">Korygująca PEF</option>
<option value="pierwotna_rr">Pierwotna RR</option>
<option value="korygujaca_rr">Korygująca RR</option>
</select>
</div>
<div class="col-sm-4 mb-2">
<label class="form-label small mb-1">Data wystawienia <span
class="text-danger">*</span></label>
<input type="date" class="form-control form-control-sm"