Initial commit

This commit is contained in:
2026-09-03 12:21:17 +02:00
commit 6d40da0410
155 changed files with 21554 additions and 0 deletions
+245
View File
@@ -0,0 +1,245 @@
@import 'tailwindcss';
@source '../**/*.blade.php';
@source '../../app/**/*.php';
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
/*
| SoftSPM 2 - system "SoftSPM Industrial" (tokeny z uj_dashboard/DESIGN.md).
| Corporate Modern + Glassmorphism, karty "Bento", przyciski-pigułki.
| Tailwind v4: konfiguracja w CSS przez @theme.
*/
@theme {
/* Fonty (rodziny) */
--font-sans: 'Inter', system-ui, sans-serif;
--font-headline-lg: 'Inter', system-ui, sans-serif;
--font-headline-md: 'Inter', system-ui, sans-serif;
--font-title-sm: 'Inter', system-ui, sans-serif;
--font-body-md: 'Inter', system-ui, sans-serif;
--font-body-sm: 'Inter', system-ui, sans-serif;
--font-data-label: 'JetBrains Mono', monospace;
/* Typografia (rozmiary) */
--text-headline-lg: 24px;
--text-headline-lg--line-height: 32px;
--text-headline-lg--font-weight: 700;
--text-headline-md: 20px;
--text-headline-md--line-height: 28px;
--text-headline-md--font-weight: 600;
--text-title-sm: 16px;
--text-title-sm--line-height: 24px;
--text-title-sm--font-weight: 600;
--text-body-md: 14px;
--text-body-md--line-height: 20px;
--text-body-sm: 12px;
--text-body-sm--line-height: 18px;
--text-data-label: 12px;
--text-data-label--line-height: 16px;
--text-data-label--font-weight: 500;
--text-data-label--letter-spacing: 0.02em;
/* Odstępy nazwane */
--spacing-gutter: 1rem;
--spacing-stack-gap: 0.75rem;
--spacing-container-padding: 1rem;
--spacing-card-inner-padding: 1rem;
--spacing-touch-target-min: 44px;
/* Cień karty "Bento" */
--shadow-card: 0 4px 12px rgb(0 0 0 / 0.08);
/* Kolory - Surface / neutralne */
--color-surface: #f7fafb;
--color-surface-bright: #f7fafb;
--color-surface-dim: #d7dadc;
--color-surface-container-lowest: #ffffff;
--color-surface-container-low: #f1f4f5;
--color-surface-container: #ebeeef;
--color-surface-container-high: #e5e9ea;
--color-surface-container-highest: #e0e3e4;
--color-surface-variant: #e0e3e4;
--color-surface-tint: #b72221;
--color-on-surface: #181c1d;
--color-on-surface-variant: #5b403d;
--color-inverse-surface: #2d3132;
--color-inverse-on-surface: #eef1f2;
--color-outline: #8f706c;
--color-outline-variant: #e3beba;
--color-background: #f7fafb;
--color-on-background: #181c1d;
/* Primary - ciemna czerwień przemysłowa */
--color-primary: #7f0009;
--color-on-primary: #ffffff;
--color-primary-container: #a61317;
--color-on-primary-container: #ffb4ac;
--color-inverse-primary: #ffb4ab;
--color-primary-fixed: #ffdad6;
--color-primary-fixed-dim: #ffb4ab;
--color-on-primary-fixed: #410002;
--color-on-primary-fixed-variant: #93000d;
/* Secondary - teal */
--color-secondary: #006876;
--color-on-secondary: #ffffff;
--color-secondary-container: #9fecfc;
--color-on-secondary-container: #0d6d7b;
--color-secondary-fixed: #a2efff;
--color-secondary-fixed-dim: #85d2e2;
--color-on-secondary-fixed: #001f25;
--color-on-secondary-fixed-variant: #004e59;
/* Tertiary */
--color-tertiary: #2f3e45;
--color-on-tertiary: #ffffff;
--color-tertiary-container: #46555d;
--color-on-tertiary-container: #b9c9d3;
--color-tertiary-fixed: #d5e5ef;
--color-tertiary-fixed-dim: #b9c9d2;
--color-on-tertiary-fixed: #0e1d24;
--color-on-tertiary-fixed-variant: #3a4951;
/* Error / statusy */
--color-error: #ba1a1a;
--color-on-error: #ffffff;
--color-error-container: #ffdad6;
--color-on-error-container: #93000a;
--color-success-active: #006876;
--color-error-alert: #ba1a1a;
}
/* Wariant 'dark' sterowany klasą na <html> (strategia class, nie media). */
@custom-variant dark (&:where(.dark, .dark *));
/* Kolory neutralne wykresów SVG (adaptują się do motywu). */
:root {
--chart-grid: #eceff0;
--chart-axis: #9aa6a8;
--chart-tick: #888888;
--chart-label: #555555;
--chart-raw: #1565c0;
}
/*
| ===== MOTYW CIEMNY =====
| Nadpisujemy tylko WARTOŚCI tokenów - wszystkie utility (bg-surface,
| text-on-surface, border-outline-variant...) używają var(--color-*), więc
| cały interfejs przełącza się automatycznie. Paleta wg Material 3 (dark)
| spójna z jasną (czerwień przemysłowa + teal). Tokeny *-fixed są wspólne
| dla obu motywów - nie nadpisujemy ich.
*/
html.dark {
/* Surface / neutralne */
--color-surface: #0f1314;
--color-surface-bright: #353a3b;
--color-surface-dim: #0f1314;
--color-surface-container-lowest: #090d0e;
--color-surface-container-low: #181c1d;
--color-surface-container: #1c2021;
--color-surface-container-high: #262a2b;
--color-surface-container-highest: #313536;
--color-surface-variant: #414b4c;
--color-surface-tint: #ffb4ab;
--color-on-surface: #e0e3e4;
--color-on-surface-variant: #c4c7c8;
--color-inverse-surface: #e0e3e4;
--color-inverse-on-surface: #2d3132;
--color-outline: #899293;
--color-outline-variant: #3f4849;
--color-background: #0f1314;
--color-on-background: #e0e3e4;
/* Primary - jaśniejsza czerwień na ciemnym tle */
--color-primary: #ffb4ab;
--color-on-primary: #690005;
--color-primary-container: #93000d;
--color-on-primary-container: #ffdad6;
--color-inverse-primary: #a61317;
/* Secondary - jaśniejszy teal */
--color-secondary: #85d2e2;
--color-on-secondary: #00363f;
--color-secondary-container: #004e59;
--color-on-secondary-container: #a2efff;
/* Tertiary */
--color-tertiary: #b9c9d3;
--color-on-tertiary: #243139;
--color-tertiary-container: #3a4951;
--color-on-tertiary-container: #d5e5ef;
/* Error / statusy */
--color-error: #ffb4ab;
--color-on-error: #690005;
--color-error-container: #93000a;
--color-on-error-container: #ffdad6;
--color-success-active: #85d2e2;
--color-error-alert: #ffb4ab;
/* Wykresy */
--chart-grid: #2a2f30;
--chart-axis: #566061;
--chart-tick: #8a9293;
--chart-label: #9aa3a4;
--chart-raw: #7fb3ff;
}
/* ===== Fonty self-hosted (air-gap: pliki w public/fonts/) ===== */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('/fonts/inter/Inter-Variable.woff2') format('woff2');
}
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400 700;
font-display: swap;
src: url('/fonts/jetbrains-mono/JetBrainsMono-Variable.woff2') format('woff2');
}
@font-face {
font-family: 'Material Symbols Outlined';
font-style: normal;
font-weight: 100 700;
font-display: block;
src: url('/fonts/material-symbols/MaterialSymbolsOutlined.woff2') format('woff2');
}
/* ===== Komponenty ===== */
.material-symbols-outlined {
font-family: 'Material Symbols Outlined';
font-weight: normal;
font-style: normal;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
/* Glassmorfizm - szklane panele/paski (mapa, wyszukiwarka) */
.glass-panel {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(227, 190, 186, 0.3);
}
html.dark .glass-panel {
background: rgba(24, 28, 29, 0.7);
border-color: rgba(137, 146, 147, 0.25);
}
/* Pill-chipy statusów (OK / Warning / Alert) */
.status-chip {
@apply inline-flex items-center gap-1 px-2 py-0.5 rounded-full font-data-label text-data-label;
}
.status-chip-ok { @apply bg-green-100 text-green-800; }
.status-chip-warn { @apply bg-amber-100 text-amber-800; }
.status-chip-alert { @apply bg-error-container text-on-error-container; }
html.dark .status-chip-ok { @apply bg-green-900/40 text-green-300; }
html.dark .status-chip-warn { @apply bg-amber-900/40 text-amber-300; }
+6
View File
@@ -0,0 +1,6 @@
/*
* SoftSPM_2 - punkt wejścia JS.
*
* Aplikacja jest server-side (Blade); interakcje ekranowe siedzą w inline
* <script> w widokach. Zasada air-gap: ZERO CDN - wszystko przez Vite.
*/
+4
View File
@@ -0,0 +1,4 @@
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
+419
View File
@@ -0,0 +1,419 @@
<x-guest-layout title="SoftSPM 2 - Logowanie">
<style>
.login-screen { min-height: 100vh; display: flex; }
/* ===== Lewa kolumna - panel wizualny ===== */
.login-visual {
position: relative;
width: 50%;
overflow: hidden;
/* Gradient turkus -> czerwień (jak hero na stronie głównej SoftSPM) */
background:
radial-gradient(circle at 86% 10%, rgba(255,255,255,.10), transparent 38%),
radial-gradient(circle at 8% 92%, rgba(255,255,255,.07), transparent 44%),
radial-gradient(120% 95% at 102% 102%, rgba(140,22,38,.50), transparent 58%),
linear-gradient(118deg, #0a5366 0%, #094a5a 33%, #33263a 64%, #5e1f2c 88%, #6e1a26 100%);
}
.login-dots {
position: absolute; inset: 0; z-index: 0;
background-image: radial-gradient(rgba(255,255,255,.06) 1px, transparent 1.4px);
background-size: 24px 24px;
-webkit-mask-image: linear-gradient(to bottom, #000 28%, transparent 92%);
mask-image: linear-gradient(to bottom, #000 28%, transparent 92%);
}
/* Okręgi w tle - bardziej wyraziste */
.login-ring, .login-ring2 { position: absolute; z-index: 0; border-radius: 50%; }
.login-ring { top: -180px; right: -150px; width: 470px; height: 470px; border: 1.5px solid rgba(255,255,255,.16); }
.login-ring2 { top: -90px; right: -60px; width: 310px; height: 310px; border: 1px solid rgba(255,255,255,.11); }
.login-ring3 { position: absolute; z-index: 0; bottom: -150px; left: -120px; width: 360px; height: 360px; border-radius: 50%; border: 1.5px solid rgba(255,255,255,.10); }
.login-mesh { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1; pointer-events: none; }
.login-brand { position: relative; z-index: 2; padding: 2.75rem 3rem; max-width: min(1200px, 90%); }
.login-brand .over {
color: #4fc3e8; font-family: 'JetBrains Mono', monospace; font-size: 12px;
letter-spacing: .18em; text-transform: uppercase; font-weight: 600;
}
.login-brand h1 {
color: #fff; font-weight: 700; line-height: 1.18; margin-top: .9rem;
font-size: clamp(26px, 3.2vw, 44px);
text-shadow: 0 2px 24px rgba(0,0,0,.25);
}
/* ===== Prawa kolumna - formularz ===== */
.login-form-panel {
width: 50%; display: flex; align-items: center; justify-content: center;
padding: 2rem; background: #f7fafb;
}
/* ===== Responsywnie: < 860px pionowo ===== */
@media (max-width: 860px) {
.login-screen { flex-direction: column; }
.login-visual { width: 100%; height: 200px; }
.login-brand { padding: 1.25rem 1.5rem; }
.login-form-panel { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
.login-mesh { opacity: .9; }
}
</style>
<div class="login-screen">
{{-- ===== LEWA: wizual + animacja ===== --}}
<div class="login-visual">
<div class="login-dots"></div>
<div class="login-ring"></div>
<div class="login-ring2"></div>
<div class="login-ring3"></div>
<canvas id="login-mesh" class="login-mesh" aria-hidden="true" data-mode="text" data-text="SoftSPM"></canvas>
<div class="login-brand">
<p class="over">Submersible Pumps Management</p>
<h1>Inteligentny Nadzór Eksploatacji Pomp Głębinowych i Raportowanie</h1>
</div>
</div>
{{-- ===== PRAWA: formularz ===== --}}
<div class="login-form-panel">
<div class="w-full" style="max-width: 360px;">
{{-- Logo - link do softspm.com (ta sama zakładka) --}}
<div class="mb-7">
<a href="https://softspm.com" class="inline-block">
<img src="{{ asset('images/logo.png') }}" alt="SoftSPM" style="width: 220px; max-width: 100%;" class="h-auto">
</a>
</div>
<h2 class="font-headline-md text-headline-md text-on-surface">Logowanie</h2>
<p class="font-body-sm text-body-sm text-on-surface-variant mt-1 mb-6">Zaloguj się do systemu SoftSPM</p>
@if ($errors->any())
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm mb-4">{{ $errors->first() }}</div>
@endif
<form method="POST" action="{{ route('login.attempt') }}" class="space-y-4" style="padding: 0;">
@csrf
{{-- Nazwa użytkownika --}}
<div class="space-y-1">
<label for="username" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Nazwa użytkownika</label>
<div class="relative">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-on-surface-variant/60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
</svg>
<input id="username" name="username" type="text" required autofocus autocomplete="username"
value="{{ old('username') }}" placeholder="Podaj nazwę użytkownika"
class="w-full bg-white border border-outline-variant pl-10 pr-3 py-3 rounded-lg focus:ring-1 focus:ring-secondary focus:border-secondary font-body-md text-on-surface transition-all">
</div>
</div>
{{-- Hasło --}}
<div class="space-y-1">
<label for="password" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Hasło</label>
<div class="relative">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-on-surface-variant/60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>
</svg>
<input id="password" name="password" type="password" required autocomplete="current-password"
placeholder="••••••••"
class="w-full bg-white border border-outline-variant pl-10 pr-3 py-3 rounded-lg focus:ring-1 focus:ring-secondary focus:border-secondary font-body-md text-on-surface transition-all">
</div>
</div>
{{-- Oddział - ukryty na czas testów (config softspm.login_department); odwracalne (ustaw null) --}}
@unless (config('softspm.login_department'))
<div class="space-y-1">
<label for="department" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Oddział</label>
<div class="relative">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-on-surface-variant/60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 21h18"/><path d="M5 21V7l8-4v18"/><path d="M19 21V11l-6-4"/><path d="M9 9v.01M9 12v.01M9 15v.01M9 18v.01"/>
</svg>
<select id="department" name="department" required
class="w-full bg-white border border-outline-variant pl-10 pr-3 py-3 rounded-lg focus:ring-1 focus:ring-secondary focus:border-secondary font-body-md text-on-surface transition-all">
@foreach (config('softspm.departments') as $dep)
<option value="{{ $dep }}" {{ old('department') === $dep ? 'selected' : '' }}>{{ $dep }}</option>
@endforeach
</select>
</div>
</div>
@endunless
<x-btn type="submit" variant="primary" class="w-full mt-2">Zaloguj</x-btn>
</form>
<p class="mt-8 text-center font-body-sm text-body-sm text-on-surface-variant/70">
© SoftSPM {{ date('Y') }} · v.2.0
</p>
</div>
</div>
</div>
{{-- ===== Animacja "flowing wave mesh" (Canvas 2D, bez bibliotek) ===== --}}
<script>
(function () {
const canvas = document.getElementById('login-mesh');
if (!canvas) return;
const ctx = canvas.getContext('2d', { alpha: true });
// ===== CONFIG (wartości referencyjne; data-mode/data-text nadpisują) =====
const CONFIG = {
mode: canvas.dataset.mode || 'text',
text: canvas.dataset.text || 'SoftSPM',
grid: 7,
alphaThreshold: 120,
fontWeight: 700,
textHeightRatio: 0.17,
textAnchor: { x: 0.07, y: 0.90 },
amplitude: 1.7,
speed: 0.0016,
color: { r: 41, g: 171, b: 226 },
opacity: 0.85,
glow: 8,
lineWidth: 1,
nodeRadius: 1.15,
drawNodes: true,
edgeFalloff: 0.55,
buckets: 6,
// tryb wave
bandCenter: 0.72,
bandWidth: 0.40,
waveCols: 90,
waveRows: 13,
connectorEvery: 3,
waves: [
{ amp: 14, freq: 1.1, speed: 0.55, phase: 0.0 },
{ amp: 8, freq: 2.3, speed: -0.9, phase: 1.7 },
{ amp: 5, freq: 3.7, speed: 1.35, phase: 3.1 },
],
};
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const rgba = (a) => 'rgba(' + CONFIG.color.r + ',' + CONFIG.color.g + ',' + CONFIG.color.b + ',' + a + ')';
let dpr = 1, cssW = 0, cssH = 0;
let nodes = [], edges = [], spotX = 0;
let particles = [];
let rafId = null, resizeTimer = null, start = 0;
function setSize() {
const rect = canvas.getBoundingClientRect();
cssW = Math.max(1, Math.round(rect.width));
cssH = Math.max(1, Math.round(rect.height));
dpr = Math.min(window.devicePixelRatio || 1, 2); // ostre linie na HiDPI
canvas.width = Math.round(cssW * dpr);
canvas.height = Math.round(cssH * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
// ===== Konstelacja tła (dryfujące punkty + linie - jak hero strony głównej) =====
function makeParticles() {
const count = Math.round(cssW * cssH / 15000);
particles = [];
for (let i = 0; i < count; i++) {
particles.push({
x: Math.random() * cssW, y: Math.random() * cssH,
vx: (Math.random() - 0.5) * 0.22, vy: (Math.random() - 0.5) * 0.22,
});
}
}
function updateParticles() {
for (const p of particles) {
p.x += p.vx; p.y += p.vy;
if (p.x < 0 || p.x > cssW) p.vx = -p.vx;
if (p.y < 0 || p.y > cssH) p.vy = -p.vy;
}
}
function drawConstellation() {
const maxD = 155, maxD2 = maxD * maxD;
ctx.lineWidth = 1;
for (let i = 0; i < particles.length; i++) {
const a = particles[i];
for (let j = i + 1; j < particles.length; j++) {
const b = particles[j];
const dx = a.x - b.x, dy = a.y - b.y, d2 = dx * dx + dy * dy;
if (d2 < maxD2) {
ctx.strokeStyle = 'rgba(180,214,228,' + ((1 - Math.sqrt(d2) / maxD) * 0.16) + ')';
ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke();
}
}
}
ctx.fillStyle = 'rgba(205,230,240,.32)';
ctx.beginPath();
for (const p of particles) { ctx.moveTo(p.x + 1.3, p.y); ctx.arc(p.x, p.y, 1.3, 0, Math.PI * 2); }
ctx.fill();
}
// ===== Budowa modelu z napisu =====
function buildText() {
const off = document.createElement('canvas');
off.width = cssW; off.height = cssH;
const o = off.getContext('2d');
const fontSize = Math.max(12, Math.round(cssH * CONFIG.textHeightRatio));
o.clearRect(0, 0, cssW, cssH);
o.fillStyle = '#fff';
o.textBaseline = 'alphabetic';
o.textAlign = 'left';
o.font = CONFIG.fontWeight + ' ' + fontSize + "px 'Inter', system-ui, sans-serif";
o.fillText(CONFIG.text, cssW * CONFIG.textAnchor.x, cssH * CONFIG.textAnchor.y);
const data = o.getImageData(0, 0, cssW, cssH).data;
nodes = []; edges = [];
const map = new Map();
const g = CONFIG.grid;
let minX = 1e9, maxX = -1e9;
for (let y = 0; y < cssH; y += g) {
const row = (y / g) | 0;
for (let x = 0; x < cssW; x += g) {
if (data[(y * cssW + x) * 4 + 3] >= CONFIG.alphaThreshold) {
const col = (x / g) | 0;
map.set(col + '_' + row, nodes.length);
nodes.push({ bx: x, by: y, x: x, y: y, col: col, row: row, ph: col * 0.7 + row * 0.9 });
if (x < minX) minX = x;
if (x > maxX) maxX = x;
}
}
}
buildEdges(map, true);
spotX = nodes.length ? (minX + maxX) / 2 : cssW * 0.3;
}
// ===== Budowa modelu pasma fal =====
function buildWave() {
nodes = []; edges = [];
const map = new Map();
const cols = CONFIG.waveCols, rows = CONFIG.waveRows;
const bandTop = cssH * (CONFIG.bandCenter - CONFIG.bandWidth / 2);
const bandH = cssH * CONFIG.bandWidth;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
const x = cols > 1 ? (c / (cols - 1)) * cssW : 0;
const y = rows > 1 ? bandTop + (r / (rows - 1)) * bandH : bandTop;
map.set(c + '_' + r, nodes.length);
nodes.push({ bx: x, by: y, x: x, y: y, col: c, row: r, ph: c * 0.3 + r * 0.5 });
}
}
buildEdges(map, false);
spotX = cssW * 0.5;
}
function buildEdges(map, triangular) {
for (const n of nodes) {
const i = map.get(n.col + '_' + n.row);
const right = map.get((n.col + 1) + '_' + n.row);
if (right !== undefined) edges.push([i, right]);
if (triangular) {
const down = map.get(n.col + '_' + (n.row + 1));
const diag = map.get((n.col + 1) + '_' + (n.row + 1));
if (down !== undefined) edges.push([i, down]);
if (diag !== undefined) edges.push([i, diag]);
} else if (n.col % CONFIG.connectorEvery === 0) {
const down = map.get(n.col + '_' + (n.row + 1));
if (down !== undefined) edges.push([i, down]);
}
}
}
function build() {
if (CONFIG.mode === 'wave') buildWave(); else buildText();
}
// ===== Ruch węzłów =====
function updateNodes(t) {
if (CONFIG.mode === 'wave') {
for (const n of nodes) {
let dy = 0;
for (const w of CONFIG.waves) {
dy += w.amp * Math.sin((n.col / CONFIG.waveCols) * Math.PI * 2 * w.freq + t * w.speed + w.phase + n.row * 0.1);
}
n.x = n.bx;
n.y = n.by + dy;
}
} else {
const A = CONFIG.amplitude;
for (const n of nodes) {
n.x = n.bx + A * Math.sin(t + n.ph);
n.y = n.by + A * Math.cos(t * 1.1 + n.ph * 0.8);
}
}
}
// ===== Rysowanie (kwantyzacja jasności do kubełków - 1 stroke/kubełek) =====
function draw() {
ctx.clearRect(0, 0, cssW, cssH);
ctx.shadowBlur = 0;
drawConstellation(); // tło konstelacji (bez poświaty)
const B = CONFIG.buckets;
const bins = [];
for (let i = 0; i < B; i++) bins.push([]);
for (const e of edges) {
const a = nodes[e[0]], b = nodes[e[1]];
const nx = Math.abs((a.x + b.x) / 2 - spotX) / cssW; // odległość poziomą od reflektora
let f = 1 - CONFIG.edgeFalloff * (2 * nx) * (2 * nx); // reflektor
if (f < 0.05) f = 0.05; else if (f > 1) f = 1;
bins[Math.min(B - 1, Math.floor((1 - f) * B))].push(e);
}
ctx.lineWidth = CONFIG.lineWidth;
ctx.lineCap = 'round';
ctx.shadowColor = rgba(CONFIG.opacity);
ctx.shadowBlur = CONFIG.glow;
for (let bi = 0; bi < B; bi++) {
const list = bins[bi];
if (!list.length) continue;
ctx.strokeStyle = rgba(CONFIG.opacity * (1 - (bi + 0.5) / B));
ctx.beginPath();
for (const e of list) {
const a = nodes[e[0]], b = nodes[e[1]];
ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y);
}
ctx.stroke();
}
if (CONFIG.drawNodes) {
ctx.shadowBlur = CONFIG.glow * 0.6;
ctx.fillStyle = rgba(CONFIG.opacity);
ctx.beginPath();
const r = CONFIG.nodeRadius;
for (const n of nodes) {
ctx.moveTo(n.x + r, n.y);
ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
}
ctx.fill();
}
ctx.shadowBlur = 0;
}
function loop(now) {
updateNodes((now - start) * CONFIG.speed);
updateParticles();
draw();
rafId = requestAnimationFrame(loop);
}
function init() {
if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
setSize();
makeParticles();
build();
if (reduce) { updateNodes(0); draw(); return; } // statyczny render
start = performance.now();
rafId = requestAnimationFrame(loop);
}
function onResize() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(init, 150);
}
window.addEventListener('resize', onResize);
// Czekaj na fonty (czytelny napis), potem buduj model.
if (document.fonts && document.fonts.ready) {
document.fonts.ready.then(init);
} else {
init();
}
})();
</script>
</x-guest-layout>
@@ -0,0 +1,90 @@
<x-app-layout :active="'aggregate-selection.index'" title="SoftSPM 2 - Wybór agregatu pompowego">
@php $fmt = fn ($v, $d = 2) => $v === null || $v === '' ? '-' : number_format(round((float) $v, $d), $d, ',', ' '); @endphp
<div class="space-y-stack-gap">
<div>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Wybór agregatu pompowego</h1>
<p class="font-body-sm text-body-sm text-on-surface-variant mt-1">Wysokość podnoszenia dla posiadanych typów pomp</p>
</div>
{{-- Parametry doboru --}}
<form method="GET" action="{{ route('aggregate-selection.index') }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<p class="font-data-label text-data-label text-on-surface-variant tracking-wide mb-3">Wprowadź parametry poszukiwanej pompy</p>
<div class="flex flex-wrap items-end gap-4">
<div class="space-y-1">
<label for="q" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Zadana wydajność Q<sub>sz</sub> [m³/h]</label>
<input id="q" name="q" type="text" inputmode="decimal" required value="{{ $q }}" placeholder="np. 120"
class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface focus:ring-1 focus:ring-primary focus:border-primary">
</div>
<div class="space-y-1">
<label for="h" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Zadana wysokość podnoszenia H [m] <span class="normal-case text-on-surface-variant/60">(opcjonalnie)</span></label>
<input id="h" name="h" type="text" inputmode="decimal" value="{{ $h }}" placeholder="np. 80"
class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface focus:ring-1 focus:ring-primary focus:border-primary">
</div>
<button type="submit"
class="bg-secondary text-on-secondary px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:opacity-90 transition-all active:scale-95">
Szukaj
</button>
</div>
</form>
@if ($searched)
{{-- Wykres nałożonych krzywych H --}}
@if ($chart)
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-2">Wysokość podnoszenia dla posiadanych typów pomp</h2>
<x-selection-chart :chart="$chart" :q="$qVal" />
</div>
@endif
{{-- Lista pomp dla zadanego Q (ranking po sprawności) --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="px-card-inner-padding pt-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary">
Lista pomp dla zadanego Q
<span class="font-body-sm text-body-sm text-on-surface-variant">(malejąco wg sprawności)</span>
</h2>
</div>
<table class="w-full text-left mt-2">
<thead class="bg-secondary/10 border-y border-outline-variant/40">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-4 py-3 w-12">Lp.</th>
<th class="px-4 py-3">Typ pompy</th>
<th class="px-4 py-3">Typ silnika</th>
<th class="px-4 py-3 text-right">Q<sub>n</sub> [m³/h]</th>
<th class="px-4 py-3 text-right">Q<sub>sz</sub> [m³/h]</th>
<th class="px-4 py-3 text-right">H(Q<sub>sz</sub>) [m]</th>
<th class="px-4 py-3 text-right">&eta;(Q<sub>sz</sub>) [-]</th>
<th class="px-4 py-3 text-right">P<sub>s</sub> [kW]</th>
<th class="px-4 py-3 text-right">Napięcie [V]</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30 font-body-md text-body-md">
@forelse ($rows as $row)
<tr class="hover:bg-surface-container transition-colors cursor-pointer"
onclick="location.href='{{ route('aggregates.show', ['pump' => $row->pump_id] + ($row->engine_id ? ['engine' => $row->engine_id] : [])) }}'">
<td class="px-4 py-3 font-data-label text-on-surface-variant">{{ $row->lp }}.</td>
<td class="px-4 py-3 font-semibold text-on-surface">{{ $row->pump_name }}</td>
<td class="px-4 py-3">{{ $row->engine_name ?? '-' }}</td>
<td class="px-4 py-3 text-right font-data-label">{{ $fmt($row->q_n, 3) }}</td>
<td class="px-4 py-3 text-right font-data-label">{{ $fmt($qVal, 3) }}</td>
<td class="px-4 py-3 text-right font-data-label">{{ $fmt($row->h_val, 2) }}</td>
<td class="px-4 py-3 text-right font-data-label">{{ $fmt($row->eta_val, 2) }}</td>
<td class="px-4 py-3 text-right font-data-label">{{ $fmt($row->power, 2) }}</td>
<td class="px-4 py-3 text-right font-data-label">{{ $row->voltage ?? '-' }}</td>
</tr>
@empty
<tr>
<td colspan="9" class="px-4 py-8 text-center font-body-md text-body-md text-on-surface-variant">
Brak pomp spełniających wprowadzone parametry.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@endif
</div>
</x-app-layout>
@@ -0,0 +1,46 @@
<x-app-layout :active="'aggregates.index'" title="SoftSPM 2 - Przypisanie silników">
<div class="max-w-3xl space-y-stack-gap">
<div class="flex items-center gap-2">
<a href="{{ route('aggregates.show', $pump) }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Wróć</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Silniki dla pompy: {{ $pump->PUP_NAME }}</h1>
</div>
<form method="POST" action="{{ route('aggregates.update', $pump) }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
@csrf
@method('PUT')
<table class="w-full text-left">
<thead class="bg-secondary/10 border-b border-outline-variant/40">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-4 py-2 w-12"></th>
<th class="px-4 py-2">Typ silnika</th>
<th class="px-4 py-2 text-right">Napięcie [V]</th>
<th class="px-4 py-2 text-right">Moc [kW]</th>
<th class="px-4 py-2">Producent</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30 font-body-md text-body-md">
@foreach ($allEngines as $e)
<tr class="hover:bg-surface-container transition-colors">
<td class="px-4 py-2">
<input type="checkbox" name="engines[]" value="{{ $e->ENG_ID }}"
{{ in_array((int) $e->ENG_ID, $assigned, true) ? 'checked' : '' }}
class="w-4 h-4 accent-secondary">
</td>
<td class="px-4 py-2 font-semibold text-on-surface">{{ $e->ENG_NAME }}</td>
<td class="px-4 py-2 text-right font-data-label">{{ $e->ENG_VOLTAGE }}</td>
<td class="px-4 py-2 text-right font-data-label">{{ $e->ENG_POWER }}</td>
<td class="px-4 py-2">{{ $e->manufacturer?->MCF_NAME }}</td>
</tr>
@endforeach
</tbody>
</table>
<div class="flex items-center gap-2 p-card-inner-padding border-t border-outline-variant/40">
<button type="submit" class="bg-primary text-on-primary px-6 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">Zapisz</button>
<a href="{{ route('aggregates.show', $pump) }}" class="px-5 py-2.5 rounded-full font-title-sm text-title-sm text-on-surface-variant hover:bg-surface-container transition-all">Anuluj</a>
</div>
</form>
</div>
</x-app-layout>
@@ -0,0 +1,79 @@
<x-app-layout :active="'aggregates.index'" title="SoftSPM 2 - Katalog agregatow">
@php $fmt = fn ($v, $d = 3) => $v === null ? '-' : number_format(round((float) $v, $d), $d, ',', ' '); @endphp
<div class="space-y-stack-gap">
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('status') }}</div>
@endif
<div>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Katalog agregatow</h1>
<p class="font-body-sm text-body-sm text-on-surface-variant mt-1">Wynikow: <span class="font-data-label">{{ $aggregates->total() }}</span></p>
</div>
{{-- Wyszukiwarka pomp --}}
<form method="GET" action="{{ route('aggregates.index') }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<div class="flex flex-wrap items-end gap-4">
<div class="space-y-1">
<label for="by" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Kryterium</label>
<select id="by" name="by" class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
<option value="name" {{ $by === 'name' ? 'selected' : '' }}>Typ pompy</option>
<option value="flow" {{ $by === 'flow' ? 'selected' : '' }}>Wydajnosc Q</option>
</select>
</div>
<div class="space-y-1">
<label for="q" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Szukaj</label>
<input id="q" name="q" type="text" value="{{ $q }}" placeholder="typ lub Q"
class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
</div>
<div class="space-y-1">
<label for="mcf" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Producent</label>
<select id="mcf" name="mcf" class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
<option value="{{ $mcfAll }}" {{ (int) $mcf === $mcfAll ? 'selected' : '' }}>Wszyscy</option>
@foreach ($manufacturers as $id => $name)
<option value="{{ $id }}" {{ (int) $mcf === (int) $id ? 'selected' : '' }}>{{ $name }}</option>
@endforeach
</select>
</div>
<button type="submit" class="bg-secondary text-on-secondary px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:opacity-90 active:scale-95 transition-all">Szukaj</button>
</div>
</form>
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<table class="w-full text-left">
<thead class="bg-secondary/10 border-b border-outline-variant/40">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-4 py-3 w-12">Lp.</th>
<x-sortable-th column="name">Typ pompy</x-sortable-th>
<x-sortable-th column="flow" align="right">Q<sub>n</sub> [m³/h]</x-sortable-th>
<x-sortable-th column="mcf">Producent</x-sortable-th>
<x-sortable-th column="engines" align="right">Silniki</x-sortable-th>
<th class="px-4 py-3 text-right">Akcje</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30 font-body-md text-body-md">
@forelse ($aggregates as $a)
<tr class="hover:bg-surface-container transition-colors">
<td class="px-4 py-3 font-data-label text-on-surface-variant">{{ $aggregates->firstItem() + $loop->index }}.</td>
<td class="px-4 py-3 font-semibold text-on-surface">
<a href="{{ route('aggregates.show', $a) }}" class="hover:text-primary">{{ $a->PUP_NAME }}</a>
</td>
<td class="px-4 py-3 text-right font-data-label">{{ $fmt($a->PUP_Q_N) }}</td>
<td class="px-4 py-3">{{ $a->manufacturer?->MCF_NAME }}</td>
<td class="px-4 py-3 text-right font-data-label">{{ $a->engines_count }}</td>
<td class="px-4 py-3 text-right">
<a href="{{ route('aggregates.show', $a) }}" class="font-body-sm text-body-sm text-secondary hover:underline">Szczegoly</a>
</td>
</tr>
@empty
<tr><td colspan="6" class="px-4 py-8 text-center font-body-md text-body-md text-on-surface-variant">Brak pomp dla wybranych kryteriow.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div>{{ $aggregates->links() }}</div>
</div>
</x-app-layout>
@@ -0,0 +1,96 @@
<x-app-layout :active="'aggregates.index'" title="SoftSPM 2 - Agregat {{ $pump->PUP_NAME }}">
@php $fmt = fn ($v, $d = 2) => $v === null ? '-' : number_format(round((float) $v, $d), $d, ',', ' '); @endphp
<div class="space-y-stack-gap">
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('status') }}</div>
@endif
<div class="flex flex-wrap items-center justify-between gap-3">
<div class="flex items-center gap-2">
<a href="{{ route('aggregates.index') }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Katalog agregatow</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">{{ $pump->PUP_NAME }}</h1>
</div>
<a href="{{ route('aggregates.edit', $pump) }}"
class="bg-secondary text-on-secondary px-4 py-2 rounded-full font-title-sm text-title-sm hover:opacity-90 active:scale-95 transition-all">Przypisz silniki</a>
</div>
{{-- Wykresy --}}
<div class="grid grid-cols-1 md:grid-cols-2 gap-stack-gap">
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-2">Wysokosc podnoszenia</h2>
@if ($hDrawable)
<x-pump-curve :plot="$hPlot" x-label="Q [m³/h]" y-label="H [m]" />
@else
<p class="font-body-sm text-body-sm text-on-surface-variant py-6 text-center">Brak parametrow energetycznych dla pompy.</p>
@endif
</div>
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-2">Sprawnosc</h2>
@if ($etaDrawable)
<x-pump-curve :plot="$etaPlot" x-label="Q [m³/h]" y-label="η [-]" />
@else
<p class="font-body-sm text-body-sm text-on-surface-variant py-6 text-center">Brak parametrow energetycznych dla pompy.</p>
@endif
</div>
</div>
{{-- Silniki dla pompy (kolumny wg oryginalu: Typ silnika / Napiecie / Producent) --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="px-card-inner-padding pt-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary">Lista silnikow dla pompy: {{ $pump->PUP_NAME }}</h2>
</div>
<table class="w-full text-left mt-2">
<thead class="bg-secondary/10 border-y border-outline-variant/40">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-4 py-2">Typ silnika</th>
<th class="px-4 py-2 text-right">Napiecie [V]</th>
<th class="px-4 py-2">Producent</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30 font-body-md text-body-md">
@forelse ($engines as $e)
<tr class="cursor-pointer transition-colors {{ $engine && $engine->ENG_ID === $e->ENG_ID ? 'bg-secondary-container/60' : 'hover:bg-surface-container' }}"
onclick="location.href='{{ route('aggregates.show', ['pump' => $pump->PUP_ID, 'engine' => $e->ENG_ID]) }}'">
<td class="px-4 py-2 font-semibold text-on-surface">{{ $e->ENG_NAME }}</td>
<td class="px-4 py-2 text-right font-data-label">{{ $e->ENG_VOLTAGE }}</td>
<td class="px-4 py-2">{{ $e->MCF_NAME }}</td>
</tr>
@empty
<tr><td colspan="3" class="px-4 py-4 text-center font-body-sm text-on-surface-variant">Brak przypisanych silnikow - kliknij "Przypisz silniki".</td></tr>
@endforelse
</tbody>
</table>
</div>
{{-- Karta zestawu pompa + silnik --}}
@if ($engine)
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-primary mb-3">{{ $pump->PUP_NAME }} + {{ $engine->ENG_NAME }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<a href="{{ route('pumps.show', $pump) }}" class="block group">
<p class="font-data-label text-data-label text-on-surface-variant tracking-wide mb-2 group-hover:text-primary">Pompa →</p>
<dl class="grid grid-cols-2 gap-y-1 font-body-md text-body-md">
<dt class="text-on-surface-variant">Typ pompy</dt><dd class="font-semibold">{{ $pump->PUP_NAME }}</dd>
<dt class="text-on-surface-variant">Producent</dt><dd>{{ $pump->manufacturer?->MCF_NAME ?? '-' }}</dd>
<dt class="text-on-surface-variant">Q<sub>n</sub> [m³/h]</dt><dd class="font-data-label">{{ $fmt($pump->PUP_Q_N, 3) }}</dd>
<dt class="text-on-surface-variant">H<sub>p</sub> [m]</dt><dd class="font-data-label">{{ $fmt($pump->PUP_H_P) }}</dd>
<dt class="text-on-surface-variant">η<sub>max</sub> [-]</dt><dd class="font-data-label">{{ $fmt($pump->PUP_ETA_MAX) }}</dd>
</dl>
</a>
<a href="{{ route('engines.show', $engine) }}" class="block group">
<p class="font-data-label text-data-label text-on-surface-variant tracking-wide mb-2 group-hover:text-primary">Silnik →</p>
<dl class="grid grid-cols-2 gap-y-1 font-body-md text-body-md">
<dt class="text-on-surface-variant">Typ silnika</dt><dd class="font-semibold">{{ $engine->ENG_NAME }}</dd>
<dt class="text-on-surface-variant">Producent</dt><dd>{{ $engine->manufacturer?->MCF_NAME ?? '-' }}</dd>
<dt class="text-on-surface-variant">Napiecie [V]</dt><dd class="font-data-label">{{ $engine->ENG_VOLTAGE ?? '-' }}</dd>
<dt class="text-on-surface-variant">Moc [kW]</dt><dd class="font-data-label">{{ $fmt($engine->ENG_POWER) }}</dd>
<dt class="text-on-surface-variant">Sprawnosc η [-]</dt><dd class="font-data-label">{{ $fmt($engine->ENG_ETA) }}</dd>
</dl>
</a>
</div>
</div>
@endif
</div>
</x-app-layout>
@@ -0,0 +1,47 @@
<x-app-layout :active="'cables.index'" title="SoftSPM 2 - Nowy typ kabla">
<div class="max-w-2xl space-y-stack-gap">
<div class="flex items-center gap-2">
<a href="{{ route('cables.index') }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Wróć</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Nowy typ kabla</h1>
</div>
@if ($errors->any())
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ $errors->first() }}</div>
@endif
<form method="POST" action="{{ route('cables.store') }}" enctype="multipart/form-data"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding space-y-4">
@csrf
<x-well-field name="name" label="Typ kabla" :value="old('name')" required />
<x-well-select name="manufacturer_id" label="Producent" :options="$manufacturers" :selected="old('manufacturer_id')" required placeholder="- wybierz -" />
<div class="space-y-1">
<label for="description" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Opis</label>
<textarea id="description" name="description" rows="3"
class="w-full bg-surface-container-low border border-outline-variant px-3 py-2 rounded focus:ring-1 focus:ring-primary focus:border-primary font-body-md text-on-surface">{{ old('description') }}</textarea>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1">
<label for="catalogue_file" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Dokumentacja katalogowa</label>
<input id="catalogue_file" name="catalogue_file" type="file" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
class="w-full font-body-sm text-body-sm text-on-surface file:mr-3 file:px-4 file:py-2 file:rounded-full file:border-0 file:bg-secondary file:text-on-secondary file:font-title-sm file:cursor-pointer hover:file:opacity-90">
<p class="font-body-sm text-body-sm text-on-surface-variant/70">PDF lub Office, do 8 MB.</p>
</div>
<div class="space-y-1">
<label for="image_file" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Zdjęcie</label>
<input id="image_file" name="image_file" type="file" accept="image/*"
class="w-full font-body-sm text-body-sm text-on-surface file:mr-3 file:px-4 file:py-2 file:rounded-full file:border-0 file:bg-secondary file:text-on-secondary file:font-title-sm file:cursor-pointer hover:file:opacity-90">
<p class="font-body-sm text-body-sm text-on-surface-variant/70">JPG/PNG, do 8 MB.</p>
</div>
</div>
<div class="flex items-center gap-2 pt-2">
<button type="submit" class="bg-primary text-on-primary px-6 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">Zapisz</button>
<a href="{{ route('cables.index') }}" class="px-5 py-2.5 rounded-full font-title-sm text-title-sm text-on-surface-variant hover:bg-surface-container transition-all">Anuluj</a>
</div>
</form>
</div>
</x-app-layout>
@@ -0,0 +1,56 @@
<x-app-layout :active="'cables.index'" title="SoftSPM 2 - Edycja: {{ $cable->EQU_NAME }}">
<div class="max-w-2xl space-y-stack-gap">
<div class="flex items-center gap-2">
<a href="{{ route('cables.show', $cable) }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Wróć</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Edycja typu kabla</h1>
</div>
@if ($errors->any())
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ $errors->first() }}</div>
@endif
<form method="POST" action="{{ route('cables.update', $cable) }}" enctype="multipart/form-data"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding space-y-4">
@csrf
@method('PUT')
<x-well-field name="name" label="Typ kabla" :value="old('name', $cable->EQU_NAME)" required />
<x-well-select name="manufacturer_id" label="Producent" :options="$manufacturers" :selected="old('manufacturer_id', $cable->EQU_MCF_ID)" required placeholder="- wybierz -" />
<div class="space-y-1">
<label for="description" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Opis</label>
<textarea id="description" name="description" rows="3"
class="w-full bg-surface-container-low border border-outline-variant px-3 py-2 rounded focus:ring-1 focus:ring-primary focus:border-primary font-body-md text-on-surface">{{ old('description', $cable->EQU_DESCRIPTION) }}</textarea>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1">
<label for="catalogue_file" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Dokumentacja katalogowa</label>
<input id="catalogue_file" name="catalogue_file" type="file" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
class="w-full font-body-sm text-body-sm text-on-surface file:mr-3 file:px-4 file:py-2 file:rounded-full file:border-0 file:bg-secondary file:text-on-secondary file:font-title-sm file:cursor-pointer hover:file:opacity-90">
@if ($document)
<p class="font-body-sm text-body-sm text-on-surface-variant/70">Obecny: <a href="{{ route('equipment.file', $document->EQF_ID) }}" target="_blank" class="text-secondary hover:underline">{{ $document->EQF_FILE_NAME }}</a>. Wgranie nowego zastąpi.</p>
@else
<p class="font-body-sm text-body-sm text-on-surface-variant/70">Brak. PDF lub Office, do 8 MB.</p>
@endif
</div>
<div class="space-y-1">
<label for="image_file" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Zdjęcie</label>
<input id="image_file" name="image_file" type="file" accept="image/*"
class="w-full font-body-sm text-body-sm text-on-surface file:mr-3 file:px-4 file:py-2 file:rounded-full file:border-0 file:bg-secondary file:text-on-secondary file:font-title-sm file:cursor-pointer hover:file:opacity-90">
@if ($image)
<p class="font-body-sm text-body-sm text-on-surface-variant/70">Obecne: <a href="{{ route('equipment.file', $image->EQF_ID) }}" target="_blank" class="text-secondary hover:underline">{{ $image->EQF_FILE_NAME }}</a>. Wgranie nowego zastąpi.</p>
@else
<p class="font-body-sm text-body-sm text-on-surface-variant/70">Brak. JPG/PNG, do 8 MB.</p>
@endif
</div>
</div>
<div class="flex items-center gap-2 pt-2">
<button type="submit" class="bg-primary text-on-primary px-6 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">Zapisz zmiany</button>
<a href="{{ route('cables.show', $cable) }}" class="px-5 py-2.5 rounded-full font-title-sm text-title-sm text-on-surface-variant hover:bg-surface-container transition-all">Anuluj</a>
</div>
</form>
</div>
</x-app-layout>
@@ -0,0 +1,75 @@
<x-app-layout :active="'cables.index'" title="SoftSPM 2 - Katalog kabli">
<div class="space-y-stack-gap">
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('status') }}</div>
@endif
<div class="flex items-end justify-between">
<div>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Katalog kabli</h1>
<p class="font-body-sm text-body-sm text-on-surface-variant mt-1">Wyników: <span class="font-data-label">{{ $cables->total() }}</span></p>
</div>
<a href="{{ route('cables.create') }}"
class="bg-primary text-on-primary px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">
Dodaj typ kabla
</a>
</div>
{{-- Wyszukiwarka --}}
<form method="GET" action="{{ route('cables.index') }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<div class="flex flex-wrap items-end gap-4">
<div class="space-y-1">
<label for="q" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Typ kabla</label>
<input id="q" name="q" type="text" value="{{ $q }}" placeholder="nazwa / typ"
class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
</div>
<div class="space-y-1">
<label for="mcf" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Producent</label>
<select id="mcf" name="mcf" class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
<option value="{{ $mcfAll }}" {{ (int) $mcf === $mcfAll ? 'selected' : '' }}>Wszyscy</option>
@foreach ($manufacturers as $id => $name)
<option value="{{ $id }}" {{ (int) $mcf === (int) $id ? 'selected' : '' }}>{{ $name }}</option>
@endforeach
</select>
</div>
<button type="submit" class="bg-secondary text-on-secondary px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:opacity-90 transition-all active:scale-95">Szukaj</button>
</div>
</form>
{{-- Tabela --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<table class="w-full text-left">
<thead class="bg-secondary/10 border-b border-outline-variant/40">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-4 py-3 w-12">Lp.</th>
<x-sortable-th column="name">Typ kabla</x-sortable-th>
<x-sortable-th column="mcf">Producent</x-sortable-th>
<th class="px-4 py-3 text-right">Akcje</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30 font-body-md text-body-md">
@forelse ($cables as $cable)
<tr class="hover:bg-surface-container transition-colors">
<td class="px-4 py-3 font-data-label text-on-surface-variant">{{ $cables->firstItem() + $loop->index }}.</td>
<td class="px-4 py-3 font-semibold text-on-surface">
<a href="{{ route('cables.show', $cable) }}" class="hover:text-primary">{{ $cable->EQU_NAME }}</a>
</td>
<td class="px-4 py-3">{{ $cable->manufacturer?->MCF_NAME }}</td>
<td class="px-4 py-3 text-right whitespace-nowrap">
<a href="{{ route('cables.show', $cable) }}" class="font-body-sm text-body-sm text-secondary hover:underline">Szczegóły</a>
<span class="text-outline-variant mx-1">·</span>
<a href="{{ route('cables.edit', $cable) }}" class="font-body-sm text-body-sm text-secondary hover:underline">Edytuj</a>
</td>
</tr>
@empty
<tr><td colspan="4" class="px-4 py-8 text-center font-body-md text-body-md text-on-surface-variant">Brak kabli dla wybranych kryteriów.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div>{{ $cables->links() }}</div>
</div>
</x-app-layout>
@@ -0,0 +1,61 @@
<x-app-layout :active="'cables.index'" title="SoftSPM 2 - {{ $cable->EQU_NAME }}">
<div class="space-y-stack-gap">
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('status') }}</div>
@endif
@if (session('error'))
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('error') }}</div>
@endif
<div class="flex flex-wrap items-center justify-between gap-3">
<div class="flex items-center gap-2">
<a href="{{ route('cables.index') }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Katalog kabli</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">{{ $cable->EQU_NAME }}</h1>
</div>
<div class="flex items-center gap-2">
<a href="{{ route('cables.edit', $cable) }}"
class="bg-secondary text-on-secondary px-4 py-2 rounded-full font-title-sm text-title-sm hover:opacity-90 transition-all active:scale-95">Edytuj</a>
@unless ($inStock)
<x-confirm-delete :action="route('cables.destroy', $cable)"
message="Czy na pewno usunąć typ kabla „{{ $cable->EQU_NAME }}”?" />
@endunless
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-stack-gap">
{{-- Dane --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-3">Dane</h2>
<dl class="grid grid-cols-3 gap-x-4 gap-y-2 font-body-md text-body-md">
<dt class="text-on-surface-variant">Typ kabla</dt><dd class="col-span-2 font-semibold">{{ $cable->EQU_NAME }}</dd>
<dt class="text-on-surface-variant">Producent</dt><dd class="col-span-2">{{ $cable->manufacturer?->MCF_NAME ?? '-' }}</dd>
<dt class="text-on-surface-variant">Opis</dt><dd class="col-span-2">{!! $cable->EQU_DESCRIPTION ? nl2br(e($cable->EQU_DESCRIPTION)) : '-' !!}</dd>
@if ($document)
<dt class="text-on-surface-variant">Dokumentacja katalogowa</dt>
<dd class="col-span-2">
<a href="{{ route('equipment.file', $document->EQF_ID) }}" target="_blank"
class="text-secondary hover:underline break-all">{{ $document->EQF_FILE_NAME }}</a>
</dd>
@endif
</dl>
@if ($inStock)
<p class="mt-4 font-body-sm text-body-sm text-on-surface-variant/80">Kabel jest na stanie magazynowym - usunięcie zablokowane.</p>
@endif
</div>
{{-- Zdjęcie --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-3">Zdjęcie</h2>
@if ($image)
<img src="{{ route('equipment.file', $image->EQF_ID) }}" alt="{{ $cable->EQU_NAME }}"
class="max-w-full h-auto rounded border border-outline-variant/40">
@else
<p class="font-body-sm text-body-sm text-on-surface-variant py-6 text-center">Brak zdjęcia.</p>
@endif
</div>
</div>
</div>
</x-app-layout>
@@ -0,0 +1,38 @@
<x-app-layout :active="'engines.index'" title="SoftSPM 2 - Nowy silnik">
<div class="max-w-2xl space-y-stack-gap">
<div class="flex items-center gap-2">
<a href="{{ route('engines.index') }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Wróć</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Nowy silnik</h1>
</div>
@if ($errors->any())
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ $errors->first() }}</div>
@endif
<form method="POST" action="{{ route('engines.store') }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding space-y-4">
@csrf
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<x-well-field name="name" label="Typ silnika" :value="old('name')" required class="sm:col-span-2" />
<x-well-select name="manufacturer_id" label="Producent" :options="$manufacturers" :selected="old('manufacturer_id')" required placeholder="- wybierz -" class="sm:col-span-2" />
<x-well-field name="power" label="Moc [kW]" :value="old('power')" required />
<x-well-field name="voltage" label="Napięcie [V]" :value="old('voltage')" required />
<x-well-field name="current" label="Prąd [A]" :value="old('current')" />
<x-well-field name="eta" label="Sprawność η [-]" :value="old('eta')" />
<x-well-field name="cos_fi" label="Wsp. mocy cos(φ) [-]" :value="old('cos_fi')" />
<x-well-field name="rpm" label="Obroty [1/min]" :value="old('rpm')" />
<x-well-field name="length" label="Długość [mm]" :value="old('length')" />
<x-well-field name="weight" label="Masa [kg]" :value="old('weight')" />
<x-well-field name="diameter" label="Średnica [mm]" :value="old('diameter')" />
<x-well-field name="wires" label="Wprowadzenie przewodów" :value="old('wires')" />
</div>
<div class="flex items-center gap-2 pt-2">
<button type="submit" class="bg-primary text-on-primary px-6 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">Zapisz</button>
<a href="{{ route('engines.index') }}" class="px-5 py-2.5 rounded-full font-title-sm text-title-sm text-on-surface-variant hover:bg-surface-container transition-all">Anuluj</a>
</div>
</form>
</div>
</x-app-layout>
@@ -0,0 +1,39 @@
<x-app-layout :active="'engines.index'" title="SoftSPM 2 - Edycja silnika">
<div class="max-w-2xl space-y-stack-gap">
<div class="flex items-center gap-2">
<a href="{{ route('engines.show', $engine) }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Wróć</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Edycja silnika: {{ $engine->ENG_NAME }}</h1>
</div>
@if ($errors->any())
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ $errors->first() }}</div>
@endif
<form method="POST" action="{{ route('engines.update', $engine) }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding space-y-4">
@csrf
@method('PUT')
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<x-well-field name="name" label="Typ silnika" :value="$engine->ENG_NAME" required class="sm:col-span-2" />
<x-well-select name="manufacturer_id" label="Producent" :options="$manufacturers" :selected="$engine->ENG_MCF_ID" required class="sm:col-span-2" />
<x-well-field name="power" label="Moc [kW]" :value="$engine->ENG_POWER" required />
<x-well-field name="voltage" label="Napięcie [V]" :value="$engine->ENG_VOLTAGE" required />
<x-well-field name="current" label="Prąd [A]" :value="$engine->ENG_ELECTRICITY" />
<x-well-field name="eta" label="Sprawność η [-]" :value="$engine->ENG_ETA" />
<x-well-field name="cos_fi" label="Wsp. mocy cos(φ) [-]" :value="$engine->ENG_COS_FI" />
<x-well-field name="rpm" label="Obroty [1/min]" :value="$engine->ENG_RPM" />
<x-well-field name="length" label="Długość [mm]" :value="$engine->ENG_LENGTH" />
<x-well-field name="weight" label="Masa [kg]" :value="$engine->ENG_WEIGHT" />
<x-well-field name="diameter" label="Średnica [mm]" :value="$engine->ENG_DIAMETER" />
<x-well-field name="wires" label="Wprowadzenie przewodów" :value="$engine->ENG_WIRES" />
</div>
<div class="flex items-center gap-2 pt-2">
<button type="submit" class="bg-primary text-on-primary px-6 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">Zapisz zmiany</button>
<a href="{{ route('engines.show', $engine) }}" class="px-5 py-2.5 rounded-full font-title-sm text-title-sm text-on-surface-variant hover:bg-surface-container transition-all">Anuluj</a>
</div>
</form>
</div>
</x-app-layout>
@@ -0,0 +1,84 @@
<x-app-layout :active="'engines.index'" title="SoftSPM 2 - Katalog silników">
<div class="space-y-stack-gap">
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('status') }}</div>
@endif
<div class="flex items-end justify-between">
<div>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Katalog silników</h1>
<p class="font-body-sm text-body-sm text-on-surface-variant mt-1">Wyników: <span class="font-data-label">{{ $engines->total() }}</span></p>
</div>
<a href="{{ route('engines.create') }}"
class="bg-primary text-on-primary px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">
Dodaj silnik
</a>
</div>
<form method="GET" action="{{ route('engines.index') }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<div class="flex flex-wrap items-end gap-4">
<div class="space-y-1">
<label for="by" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Kryterium</label>
<select id="by" name="by" class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
<option value="name" {{ $by === 'name' ? 'selected' : '' }}>Typ silnika</option>
<option value="power" {{ $by === 'power' ? 'selected' : '' }}>Moc</option>
</select>
</div>
<div class="space-y-1">
<label for="q" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Szukaj</label>
<input id="q" name="q" type="text" value="{{ $q }}" placeholder="typ lub wartość Moc"
class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
</div>
<div class="space-y-1">
<label for="mcf" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Producent</label>
<select id="mcf" name="mcf" class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
<option value="{{ $mcfAll }}" {{ (int) $mcf === $mcfAll ? 'selected' : '' }}>Wszyscy</option>
@foreach ($manufacturers as $id => $name)
<option value="{{ $id }}" {{ (int) $mcf === (int) $id ? 'selected' : '' }}>{{ $name }}</option>
@endforeach
</select>
</div>
<button type="submit" class="bg-secondary text-on-secondary px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:opacity-90 transition-all active:scale-95">Szukaj</button>
</div>
</form>
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<table class="w-full text-left">
<thead class="bg-secondary/10 border-b border-outline-variant/40">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-4 py-3 w-12">Lp.</th>
<x-sortable-th column="name">Typ silnika</x-sortable-th>
<x-sortable-th column="voltage" align="right">Napięcie [V]</x-sortable-th>
<x-sortable-th column="power" align="right">Moc [kW]</x-sortable-th>
<x-sortable-th column="mcf">Producent</x-sortable-th>
<th class="px-4 py-3 text-right">Akcje</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30 font-body-md text-body-md">
@forelse ($engines as $engine)
<tr class="hover:bg-surface-container transition-colors">
<td class="px-4 py-3 font-data-label text-on-surface-variant">{{ $engines->firstItem() + $loop->index }}.</td>
<td class="px-4 py-3 font-semibold text-on-surface">
<a href="{{ route('engines.show', $engine) }}" class="hover:text-primary">{{ $engine->ENG_NAME }}</a>
</td>
<td class="px-4 py-3 text-right font-data-label">{{ $engine->ENG_VOLTAGE }}</td>
<td class="px-4 py-3 text-right font-data-label">{{ $engine->ENG_POWER }}</td>
<td class="px-4 py-3">{{ $engine->manufacturer?->MCF_NAME }}</td>
<td class="px-4 py-3 text-right whitespace-nowrap">
<a href="{{ route('engines.show', $engine) }}" class="font-body-sm text-body-sm text-secondary hover:underline">Szczegóły</a>
<span class="text-outline-variant mx-1">·</span>
<a href="{{ route('engines.edit', $engine) }}" class="font-body-sm text-body-sm text-secondary hover:underline">Edytuj</a>
</td>
</tr>
@empty
<tr><td colspan="6" class="px-4 py-8 text-center font-body-md text-body-md text-on-surface-variant">Brak silników dla wybranych kryteriów.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div>{{ $engines->links() }}</div>
</div>
</x-app-layout>
@@ -0,0 +1,75 @@
<x-app-layout :active="'engines.index'" title="SoftSPM 2 - {{ $engine->ENG_NAME }}">
@php $fmt = fn ($v, $d = 2) => $v === null ? '-' : number_format(round((float) $v, $d), $d, ',', ' '); @endphp
<div class="space-y-stack-gap">
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('status') }}</div>
@endif
<div class="flex flex-wrap items-center justify-between gap-3">
<div class="flex items-center gap-2">
<a href="{{ route('engines.index') }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Katalog silników</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">{{ $engine->ENG_NAME }}</h1>
</div>
<div class="flex items-center gap-2">
<a href="{{ route('engines.edit', $engine) }}"
class="bg-secondary text-on-secondary px-4 py-2 rounded-full font-title-sm text-title-sm hover:opacity-90 transition-all active:scale-95">Edytuj</a>
<x-confirm-delete :action="route('engines.destroy', $engine)"
message="Czy na pewno usunąć silnik „{{ $engine->ENG_NAME }}”? Tej operacji nie można cofnąć." />
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-stack-gap">
{{-- Dane techniczne --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-3">Dane techniczne silnika</h2>
<dl class="grid grid-cols-2 gap-x-4 gap-y-2 font-body-md text-body-md">
<dt class="text-on-surface-variant">Typ silnika</dt><dd class="font-semibold">{{ $engine->ENG_NAME }}</dd>
<dt class="text-on-surface-variant">Producent</dt><dd>{{ $engine->manufacturer?->MCF_NAME ?? '-' }}</dd>
<dt class="col-span-2 border-t border-outline-variant/30 mt-1 pt-1"></dt>
<dt class="text-on-surface-variant">Napięcie [V]</dt><dd class="font-data-label">{{ $engine->ENG_VOLTAGE ?? '-' }}</dd>
<dt class="text-on-surface-variant">Moc [kW]</dt><dd class="font-data-label">{{ $fmt($engine->ENG_POWER) }}</dd>
<dt class="text-on-surface-variant">Prąd [A]</dt><dd class="font-data-label">{{ $fmt($engine->ENG_ELECTRICITY) }}</dd>
<dt class="text-on-surface-variant">Sprawność η [-]</dt><dd class="font-data-label">{{ $fmt($engine->ENG_ETA) }}</dd>
<dt class="text-on-surface-variant">Wsp. mocy cos(φ) [-]</dt><dd class="font-data-label">{{ $fmt($engine->ENG_COS_FI) }}</dd>
<dt class="text-on-surface-variant">Obroty [1/min]</dt><dd class="font-data-label">{{ $fmt($engine->ENG_RPM) }}</dd>
<dt class="col-span-2 border-t border-outline-variant/30 mt-1 pt-1"></dt>
<dt class="text-on-surface-variant">Długość [mm]</dt><dd class="font-data-label">{{ $fmt($engine->ENG_LENGTH) }}</dd>
<dt class="text-on-surface-variant">Średnica [mm]</dt><dd class="font-data-label">{{ $fmt($engine->ENG_DIAMETER) }}</dd>
<dt class="text-on-surface-variant">Masa [kg]</dt><dd class="font-data-label">{{ $fmt($engine->ENG_WEIGHT) }}</dd>
<dt class="text-on-surface-variant">Wprow. przewodów</dt><dd>{{ $engine->ENG_WIRES ?? '-' }}</dd>
</dl>
</div>
{{-- Lista pomp dla silnika --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="px-card-inner-padding pt-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-2">Lista pomp dla silnika</h2>
</div>
<table class="w-full text-left">
<thead class="bg-secondary/10 border-y border-outline-variant/40">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-4 py-2">Typ pompy</th>
<th class="px-4 py-2 text-right">Q<sub>n</sub> [m³/h]</th>
<th class="px-4 py-2 text-right">H<sub>p</sub> [m]</th>
<th class="px-4 py-2">Producent</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30 font-body-md text-body-md">
@forelse ($relatedPumps as $p)
<tr>
<td class="px-4 py-1.5">{{ $p->PUP_NAME }}</td>
<td class="px-4 py-1.5 text-right font-data-label">{{ number_format(round((float) $p->q_n, 3), 3, ',', ' ') }}</td>
<td class="px-4 py-1.5 text-right font-data-label">{{ $p->PUP_H_P }}</td>
<td class="px-4 py-1.5">{{ $p->MCF_NAME }}</td>
</tr>
@empty
<tr><td colspan="4" class="px-4 py-4 text-center font-body-sm text-on-surface-variant">Brak powiązanych pomp.</td></tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</x-app-layout>
@@ -0,0 +1,47 @@
<x-app-layout :active="'metering.index'" title="SoftSPM 2 - Nowe urządzenie opomiarowania">
<div class="max-w-2xl space-y-stack-gap">
<div class="flex items-center gap-2">
<a href="{{ route('metering.index') }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Wróć</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Nowe urządzenie opomiarowania</h1>
</div>
@if ($errors->any())
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ $errors->first() }}</div>
@endif
<form method="POST" action="{{ route('metering.store') }}" enctype="multipart/form-data"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding space-y-4">
@csrf
<x-well-field name="name" label="Nazwa" :value="old('name')" required />
<x-well-select name="manufacturer_id" label="Producent" :options="$manufacturers" :selected="old('manufacturer_id')" required placeholder="- wybierz -" />
<div class="space-y-1">
<label for="description" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Opis</label>
<textarea id="description" name="description" rows="3"
class="w-full bg-surface-container-low border border-outline-variant px-3 py-2 rounded focus:ring-1 focus:ring-primary focus:border-primary font-body-md text-on-surface">{{ old('description') }}</textarea>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1">
<label for="catalogue_file" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Dokumentacja katalogowa</label>
<input id="catalogue_file" name="catalogue_file" type="file" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
class="w-full font-body-sm text-body-sm text-on-surface file:mr-3 file:px-4 file:py-2 file:rounded-full file:border-0 file:bg-secondary file:text-on-secondary file:font-title-sm file:cursor-pointer hover:file:opacity-90">
<p class="font-body-sm text-body-sm text-on-surface-variant/70">PDF lub Office, do 8 MB.</p>
</div>
<div class="space-y-1">
<label for="image_file" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Zdjęcie</label>
<input id="image_file" name="image_file" type="file" accept="image/*"
class="w-full font-body-sm text-body-sm text-on-surface file:mr-3 file:px-4 file:py-2 file:rounded-full file:border-0 file:bg-secondary file:text-on-secondary file:font-title-sm file:cursor-pointer hover:file:opacity-90">
<p class="font-body-sm text-body-sm text-on-surface-variant/70">JPG/PNG, do 8 MB.</p>
</div>
</div>
<div class="flex items-center gap-2 pt-2">
<button type="submit" class="bg-primary text-on-primary px-6 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">Zapisz</button>
<a href="{{ route('metering.index') }}" class="px-5 py-2.5 rounded-full font-title-sm text-title-sm text-on-surface-variant hover:bg-surface-container transition-all">Anuluj</a>
</div>
</form>
</div>
</x-app-layout>
@@ -0,0 +1,56 @@
<x-app-layout :active="'metering.index'" title="SoftSPM 2 - Edycja: {{ $item->EQU_NAME }}">
<div class="max-w-2xl space-y-stack-gap">
<div class="flex items-center gap-2">
<a href="{{ route('metering.show', $item) }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Wróć</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Edycja urządzenia opomiarowania</h1>
</div>
@if ($errors->any())
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ $errors->first() }}</div>
@endif
<form method="POST" action="{{ route('metering.update', $item) }}" enctype="multipart/form-data"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding space-y-4">
@csrf
@method('PUT')
<x-well-field name="name" label="Nazwa" :value="old('name', $item->EQU_NAME)" required />
<x-well-select name="manufacturer_id" label="Producent" :options="$manufacturers" :selected="old('manufacturer_id', $item->EQU_MCF_ID)" required placeholder="- wybierz -" />
<div class="space-y-1">
<label for="description" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Opis</label>
<textarea id="description" name="description" rows="3"
class="w-full bg-surface-container-low border border-outline-variant px-3 py-2 rounded focus:ring-1 focus:ring-primary focus:border-primary font-body-md text-on-surface">{{ old('description', $item->EQU_DESCRIPTION) }}</textarea>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1">
<label for="catalogue_file" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Dokumentacja katalogowa</label>
<input id="catalogue_file" name="catalogue_file" type="file" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
class="w-full font-body-sm text-body-sm text-on-surface file:mr-3 file:px-4 file:py-2 file:rounded-full file:border-0 file:bg-secondary file:text-on-secondary file:font-title-sm file:cursor-pointer hover:file:opacity-90">
@if ($document)
<p class="font-body-sm text-body-sm text-on-surface-variant/70">Obecny: <a href="{{ route('equipment.file', $document->EQF_ID) }}" target="_blank" class="text-secondary hover:underline">{{ $document->EQF_FILE_NAME }}</a>. Wgranie nowego zastąpi.</p>
@else
<p class="font-body-sm text-body-sm text-on-surface-variant/70">Brak. PDF lub Office, do 8 MB.</p>
@endif
</div>
<div class="space-y-1">
<label for="image_file" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Zdjęcie</label>
<input id="image_file" name="image_file" type="file" accept="image/*"
class="w-full font-body-sm text-body-sm text-on-surface file:mr-3 file:px-4 file:py-2 file:rounded-full file:border-0 file:bg-secondary file:text-on-secondary file:font-title-sm file:cursor-pointer hover:file:opacity-90">
@if ($image)
<p class="font-body-sm text-body-sm text-on-surface-variant/70">Obecne: <a href="{{ route('equipment.file', $image->EQF_ID) }}" target="_blank" class="text-secondary hover:underline">{{ $image->EQF_FILE_NAME }}</a>. Wgranie nowego zastąpi.</p>
@else
<p class="font-body-sm text-body-sm text-on-surface-variant/70">Brak. JPG/PNG, do 8 MB.</p>
@endif
</div>
</div>
<div class="flex items-center gap-2 pt-2">
<button type="submit" class="bg-primary text-on-primary px-6 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">Zapisz zmiany</button>
<a href="{{ route('metering.show', $item) }}" class="px-5 py-2.5 rounded-full font-title-sm text-title-sm text-on-surface-variant hover:bg-surface-container transition-all">Anuluj</a>
</div>
</form>
</div>
</x-app-layout>
@@ -0,0 +1,75 @@
<x-app-layout :active="'metering.index'" title="SoftSPM 2 - Katalog opomiarowania">
<div class="space-y-stack-gap">
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('status') }}</div>
@endif
<div class="flex items-end justify-between">
<div>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Katalog opomiarowania</h1>
<p class="font-body-sm text-body-sm text-on-surface-variant mt-1">Wyników: <span class="font-data-label">{{ $items->total() }}</span></p>
</div>
<a href="{{ route('metering.create') }}"
class="bg-primary text-on-primary px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">
Dodaj urządzenie
</a>
</div>
{{-- Wyszukiwarka --}}
<form method="GET" action="{{ route('metering.index') }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<div class="flex flex-wrap items-end gap-4">
<div class="space-y-1">
<label for="q" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Nazwa</label>
<input id="q" name="q" type="text" value="{{ $q }}" placeholder="nazwa urządzenia"
class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
</div>
<div class="space-y-1">
<label for="mcf" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Producent</label>
<select id="mcf" name="mcf" class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
<option value="{{ $mcfAll }}" {{ (int) $mcf === $mcfAll ? 'selected' : '' }}>Wszyscy</option>
@foreach ($manufacturers as $id => $name)
<option value="{{ $id }}" {{ (int) $mcf === (int) $id ? 'selected' : '' }}>{{ $name }}</option>
@endforeach
</select>
</div>
<button type="submit" class="bg-secondary text-on-secondary px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:opacity-90 transition-all active:scale-95">Szukaj</button>
</div>
</form>
{{-- Tabela --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<table class="w-full text-left">
<thead class="bg-secondary/10 border-b border-outline-variant/40">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-4 py-3 w-12">Lp.</th>
<x-sortable-th column="name">Nazwa</x-sortable-th>
<x-sortable-th column="mcf">Producent</x-sortable-th>
<th class="px-4 py-3 text-right">Akcje</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30 font-body-md text-body-md">
@forelse ($items as $item)
<tr class="hover:bg-surface-container transition-colors">
<td class="px-4 py-3 font-data-label text-on-surface-variant">{{ $items->firstItem() + $loop->index }}.</td>
<td class="px-4 py-3 font-semibold text-on-surface">
<a href="{{ route('metering.show', $item) }}" class="hover:text-primary">{{ $item->EQU_NAME }}</a>
</td>
<td class="px-4 py-3">{{ $item->manufacturer?->MCF_NAME }}</td>
<td class="px-4 py-3 text-right whitespace-nowrap">
<a href="{{ route('metering.show', $item) }}" class="font-body-sm text-body-sm text-secondary hover:underline">Szczegóły</a>
<span class="text-outline-variant mx-1">·</span>
<a href="{{ route('metering.edit', $item) }}" class="font-body-sm text-body-sm text-secondary hover:underline">Edytuj</a>
</td>
</tr>
@empty
<tr><td colspan="4" class="px-4 py-8 text-center font-body-md text-body-md text-on-surface-variant">Brak urządzeń dla wybranych kryteriów.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div>{{ $items->links() }}</div>
</div>
</x-app-layout>
@@ -0,0 +1,61 @@
<x-app-layout :active="'metering.index'" title="SoftSPM 2 - {{ $item->EQU_NAME }}">
<div class="space-y-stack-gap">
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('status') }}</div>
@endif
@if (session('error'))
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('error') }}</div>
@endif
<div class="flex flex-wrap items-center justify-between gap-3">
<div class="flex items-center gap-2">
<a href="{{ route('metering.index') }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Katalog opomiarowania</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">{{ $item->EQU_NAME }}</h1>
</div>
<div class="flex items-center gap-2">
<a href="{{ route('metering.edit', $item) }}"
class="bg-secondary text-on-secondary px-4 py-2 rounded-full font-title-sm text-title-sm hover:opacity-90 transition-all active:scale-95">Edytuj</a>
@unless ($inUse)
<x-confirm-delete :action="route('metering.destroy', $item)"
message="Czy na pewno usunąć urządzenie „{{ $item->EQU_NAME }}”?" />
@endunless
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-stack-gap">
{{-- Dane --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-3">Dane</h2>
<dl class="grid grid-cols-3 gap-x-4 gap-y-2 font-body-md text-body-md">
<dt class="text-on-surface-variant">Nazwa</dt><dd class="col-span-2 font-semibold">{{ $item->EQU_NAME }}</dd>
<dt class="text-on-surface-variant">Producent</dt><dd class="col-span-2">{{ $item->manufacturer?->MCF_NAME ?? '-' }}</dd>
<dt class="text-on-surface-variant">Opis</dt><dd class="col-span-2">{!! $item->EQU_DESCRIPTION ? nl2br(e($item->EQU_DESCRIPTION)) : '-' !!}</dd>
@if ($document)
<dt class="text-on-surface-variant">Dokumentacja katalogowa</dt>
<dd class="col-span-2">
<a href="{{ route('equipment.file', $document->EQF_ID) }}" target="_blank"
class="text-secondary hover:underline break-all">{{ $document->EQF_FILE_NAME }}</a>
</dd>
@endif
</dl>
@if ($inUse)
<p class="mt-4 font-body-sm text-body-sm text-on-surface-variant/80">Urządzenie jest użyte w stanie opomiarowania - usunięcie zablokowane.</p>
@endif
</div>
{{-- Zdjęcie --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-3">Zdjęcie</h2>
@if ($image)
<img src="{{ route('equipment.file', $image->EQF_ID) }}" alt="{{ $item->EQU_NAME }}"
class="max-w-full h-auto rounded border border-outline-variant/40">
@else
<p class="font-body-sm text-body-sm text-on-surface-variant py-6 text-center">Brak zdjęcia.</p>
@endif
</div>
</div>
</div>
</x-app-layout>
@@ -0,0 +1,52 @@
<x-app-layout :active="'piping.index'" title="SoftSPM 2 - Nowa pozycja orurowania">
<div class="max-w-2xl space-y-stack-gap">
<div class="flex items-center gap-2">
<a href="{{ route('piping.index') }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Wróć</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Nowa pozycja - orurowanie / armatura</h1>
</div>
@if ($errors->any())
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ $errors->first() }}</div>
@endif
<form method="POST" action="{{ route('piping.store') }}" enctype="multipart/form-data"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding space-y-4">
@csrf
<x-well-field name="name" label="Nazwa" :value="old('name')" required />
<x-well-select name="manufacturer_id" label="Producent" :options="$manufacturers" :selected="old('manufacturer_id')" required placeholder="- wybierz -" />
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<x-well-field name="diameter" label="Średnica [mm]" :value="old('diameter')" required />
<x-well-field name="length" label="Długość [m] / Ciśnienie [bar]" :value="old('length')" />
</div>
<div class="space-y-1">
<label for="description" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Opis</label>
<textarea id="description" name="description" rows="3"
class="w-full bg-surface-container-low border border-outline-variant px-3 py-2 rounded focus:ring-1 focus:ring-primary focus:border-primary font-body-md text-on-surface">{{ old('description') }}</textarea>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1">
<label for="catalogue_file" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Dokumentacja katalogowa</label>
<input id="catalogue_file" name="catalogue_file" type="file" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
class="w-full font-body-sm text-body-sm text-on-surface file:mr-3 file:px-4 file:py-2 file:rounded-full file:border-0 file:bg-secondary file:text-on-secondary file:font-title-sm file:cursor-pointer hover:file:opacity-90">
<p class="font-body-sm text-body-sm text-on-surface-variant/70">PDF lub Office, do 8 MB.</p>
</div>
<div class="space-y-1">
<label for="image_file" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Zdjęcie</label>
<input id="image_file" name="image_file" type="file" accept="image/*"
class="w-full font-body-sm text-body-sm text-on-surface file:mr-3 file:px-4 file:py-2 file:rounded-full file:border-0 file:bg-secondary file:text-on-secondary file:font-title-sm file:cursor-pointer hover:file:opacity-90">
<p class="font-body-sm text-body-sm text-on-surface-variant/70">JPG/PNG, do 8 MB.</p>
</div>
</div>
<div class="flex items-center gap-2 pt-2">
<button type="submit" class="bg-primary text-on-primary px-6 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">Zapisz</button>
<a href="{{ route('piping.index') }}" class="px-5 py-2.5 rounded-full font-title-sm text-title-sm text-on-surface-variant hover:bg-surface-container transition-all">Anuluj</a>
</div>
</form>
</div>
</x-app-layout>
@@ -0,0 +1,61 @@
<x-app-layout :active="'piping.index'" title="SoftSPM 2 - Edycja: {{ $item->EQU_NAME }}">
<div class="max-w-2xl space-y-stack-gap">
<div class="flex items-center gap-2">
<a href="{{ route('piping.show', $item) }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Wróć</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Edycja pozycji - orurowanie / armatura</h1>
</div>
@if ($errors->any())
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ $errors->first() }}</div>
@endif
<form method="POST" action="{{ route('piping.update', $item) }}" enctype="multipart/form-data"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding space-y-4">
@csrf
@method('PUT')
<x-well-field name="name" label="Nazwa" :value="old('name', $item->EQU_NAME)" required />
<x-well-select name="manufacturer_id" label="Producent" :options="$manufacturers" :selected="old('manufacturer_id', $item->EQU_MCF_ID)" required placeholder="- wybierz -" />
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<x-well-field name="diameter" label="Średnica [mm]" :value="old('diameter', $item->EQU_PARAM_01)" required />
<x-well-field name="length" label="Długość [m] / Ciśnienie [bar]" :value="old('length', $item->EQU_PARAM_02)" />
</div>
<div class="space-y-1">
<label for="description" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Opis</label>
<textarea id="description" name="description" rows="3"
class="w-full bg-surface-container-low border border-outline-variant px-3 py-2 rounded focus:ring-1 focus:ring-primary focus:border-primary font-body-md text-on-surface">{{ old('description', $item->EQU_DESCRIPTION) }}</textarea>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1">
<label for="catalogue_file" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Dokumentacja katalogowa</label>
<input id="catalogue_file" name="catalogue_file" type="file" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
class="w-full font-body-sm text-body-sm text-on-surface file:mr-3 file:px-4 file:py-2 file:rounded-full file:border-0 file:bg-secondary file:text-on-secondary file:font-title-sm file:cursor-pointer hover:file:opacity-90">
@if ($document)
<p class="font-body-sm text-body-sm text-on-surface-variant/70">Obecny: <a href="{{ route('equipment.file', $document->EQF_ID) }}" target="_blank" class="text-secondary hover:underline">{{ $document->EQF_FILE_NAME }}</a>. Wgranie nowego zastąpi.</p>
@else
<p class="font-body-sm text-body-sm text-on-surface-variant/70">Brak. PDF lub Office, do 8 MB.</p>
@endif
</div>
<div class="space-y-1">
<label for="image_file" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Zdjęcie</label>
<input id="image_file" name="image_file" type="file" accept="image/*"
class="w-full font-body-sm text-body-sm text-on-surface file:mr-3 file:px-4 file:py-2 file:rounded-full file:border-0 file:bg-secondary file:text-on-secondary file:font-title-sm file:cursor-pointer hover:file:opacity-90">
@if ($image)
<p class="font-body-sm text-body-sm text-on-surface-variant/70">Obecne: <a href="{{ route('equipment.file', $image->EQF_ID) }}" target="_blank" class="text-secondary hover:underline">{{ $image->EQF_FILE_NAME }}</a>. Wgranie nowego zastąpi.</p>
@else
<p class="font-body-sm text-body-sm text-on-surface-variant/70">Brak. JPG/PNG, do 8 MB.</p>
@endif
</div>
</div>
<div class="flex items-center gap-2 pt-2">
<button type="submit" class="bg-primary text-on-primary px-6 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">Zapisz zmiany</button>
<a href="{{ route('piping.show', $item) }}" class="px-5 py-2.5 rounded-full font-title-sm text-title-sm text-on-surface-variant hover:bg-surface-container transition-all">Anuluj</a>
</div>
</form>
</div>
</x-app-layout>
@@ -0,0 +1,79 @@
<x-app-layout :active="'piping.index'" title="SoftSPM 2 - Katalog orurowania i armatury">
<div class="space-y-stack-gap">
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('status') }}</div>
@endif
<div class="flex items-end justify-between">
<div>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Katalog orurowania i armatury</h1>
<p class="font-body-sm text-body-sm text-on-surface-variant mt-1">Wyników: <span class="font-data-label">{{ $items->total() }}</span></p>
</div>
<a href="{{ route('piping.create') }}"
class="bg-primary text-on-primary px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">
Dodaj pozycję
</a>
</div>
{{-- Wyszukiwarka --}}
<form method="GET" action="{{ route('piping.index') }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<div class="flex flex-wrap items-end gap-4">
<div class="space-y-1">
<label for="q" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Typ orurowania - armatury</label>
<input id="q" name="q" type="text" value="{{ $q }}" placeholder="nazwa"
class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
</div>
<div class="space-y-1">
<label for="mcf" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Producent</label>
<select id="mcf" name="mcf" class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
<option value="{{ $mcfAll }}" {{ (int) $mcf === $mcfAll ? 'selected' : '' }}>Wszyscy</option>
@foreach ($manufacturers as $id => $name)
<option value="{{ $id }}" {{ (int) $mcf === (int) $id ? 'selected' : '' }}>{{ $name }}</option>
@endforeach
</select>
</div>
<button type="submit" class="bg-secondary text-on-secondary px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:opacity-90 transition-all active:scale-95">Szukaj</button>
</div>
</form>
{{-- Tabela --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<table class="w-full text-left">
<thead class="bg-secondary/10 border-b border-outline-variant/40">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-4 py-3 w-12">Lp.</th>
<x-sortable-th column="name">Nazwa</x-sortable-th>
<x-sortable-th column="diameter" align="right">Średnica [mm]</x-sortable-th>
<x-sortable-th column="length">Długość [m] / Ciśnienie [bar]</x-sortable-th>
<x-sortable-th column="mcf">Producent</x-sortable-th>
<th class="px-4 py-3 text-right">Akcje</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30 font-body-md text-body-md">
@forelse ($items as $item)
<tr class="hover:bg-surface-container transition-colors">
<td class="px-4 py-3 font-data-label text-on-surface-variant">{{ $items->firstItem() + $loop->index }}.</td>
<td class="px-4 py-3 font-semibold text-on-surface">
<a href="{{ route('piping.show', $item) }}" class="hover:text-primary">{{ $item->EQU_NAME }}</a>
</td>
<td class="px-4 py-3 text-right font-data-label">{{ $item->EQU_PARAM_01 }}</td>
<td class="px-4 py-3 font-data-label">{{ $item->EQU_PARAM_02 }}</td>
<td class="px-4 py-3">{{ $item->manufacturer?->MCF_NAME }}</td>
<td class="px-4 py-3 text-right whitespace-nowrap">
<a href="{{ route('piping.show', $item) }}" class="font-body-sm text-body-sm text-secondary hover:underline">Szczegóły</a>
<span class="text-outline-variant mx-1">·</span>
<a href="{{ route('piping.edit', $item) }}" class="font-body-sm text-body-sm text-secondary hover:underline">Edytuj</a>
</td>
</tr>
@empty
<tr><td colspan="6" class="px-4 py-8 text-center font-body-md text-body-md text-on-surface-variant">Brak pozycji dla wybranych kryteriów.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div>{{ $items->links() }}</div>
</div>
</x-app-layout>
@@ -0,0 +1,63 @@
<x-app-layout :active="'piping.index'" title="SoftSPM 2 - {{ $item->EQU_NAME }}">
<div class="space-y-stack-gap">
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('status') }}</div>
@endif
@if (session('error'))
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('error') }}</div>
@endif
<div class="flex flex-wrap items-center justify-between gap-3">
<div class="flex items-center gap-2">
<a href="{{ route('piping.index') }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Katalog orurowania i armatury</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">{{ $item->EQU_NAME }}</h1>
</div>
<div class="flex items-center gap-2">
<a href="{{ route('piping.edit', $item) }}"
class="bg-secondary text-on-secondary px-4 py-2 rounded-full font-title-sm text-title-sm hover:opacity-90 transition-all active:scale-95">Edytuj</a>
@unless ($inStock)
<x-confirm-delete :action="route('piping.destroy', $item)"
message="Czy na pewno usunąć pozycję „{{ $item->EQU_NAME }}”?" />
@endunless
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-stack-gap">
{{-- Dane --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-3">Dane</h2>
<dl class="grid grid-cols-3 gap-x-4 gap-y-2 font-body-md text-body-md">
<dt class="text-on-surface-variant">Nazwa</dt><dd class="col-span-2 font-semibold">{{ $item->EQU_NAME }}</dd>
<dt class="text-on-surface-variant">Producent</dt><dd class="col-span-2">{{ $item->manufacturer?->MCF_NAME ?? '-' }}</dd>
<dt class="text-on-surface-variant">Średnica [mm]</dt><dd class="col-span-2 font-data-label">{{ $item->EQU_PARAM_01 ?: '-' }}</dd>
<dt class="text-on-surface-variant">Długość [m] / Ciśnienie [bar]</dt><dd class="col-span-2 font-data-label">{{ $item->EQU_PARAM_02 ?: '-' }}</dd>
<dt class="text-on-surface-variant">Opis</dt><dd class="col-span-2">{!! $item->EQU_DESCRIPTION ? nl2br(e($item->EQU_DESCRIPTION)) : '-' !!}</dd>
@if ($document)
<dt class="text-on-surface-variant">Dokumentacja katalogowa</dt>
<dd class="col-span-2">
<a href="{{ route('equipment.file', $document->EQF_ID) }}" target="_blank"
class="text-secondary hover:underline break-all">{{ $document->EQF_FILE_NAME }}</a>
</dd>
@endif
</dl>
@if ($inStock)
<p class="mt-4 font-body-sm text-body-sm text-on-surface-variant/80">Pozycja jest na stanie magazynowym - usunięcie zablokowane.</p>
@endif
</div>
{{-- Zdjęcie --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-3">Zdjęcie</h2>
@if ($image)
<img src="{{ route('equipment.file', $image->EQF_ID) }}" alt="{{ $item->EQU_NAME }}"
class="max-w-full h-auto rounded border border-outline-variant/40">
@else
<p class="font-body-sm text-body-sm text-on-surface-variant py-6 text-center">Brak zdjęcia.</p>
@endif
</div>
</div>
</div>
</x-app-layout>
@@ -0,0 +1,83 @@
<x-app-layout :active="'pumps.index'" title="SoftSPM 2 - Nowa pompa">
@php $factors = [0, 0.25, 0.55, 0.7, 1.0, 1.22, 1.35, 1.42]; @endphp
<div class="max-w-3xl space-y-stack-gap">
<div class="flex items-center gap-2">
<a href="{{ route('pumps.index') }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Wróć</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Nowa pompa</h1>
</div>
@if ($errors->any())
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ $errors->first() }}</div>
@endif
<form method="POST" action="{{ route('pumps.store') }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding space-y-6">
@csrf
{{-- Dane podstawowe --}}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<x-well-field name="name" label="Typ pompy" :value="old('name')" required class="sm:col-span-2" />
<x-well-select name="manufacturer_id" label="Producent" :options="$manufacturers" :selected="old('manufacturer_id')" required placeholder="- wybierz -" class="sm:col-span-2" />
<x-well-field name="q_n" label="Qₙ [m³/h]" :value="old('q_n')" required />
<x-well-field name="length" label="Długość [mm]" :value="old('length')" />
<x-well-field name="diameter" label="Średnica [mm]" :value="old('diameter')" />
<x-well-field name="weight" label="Masa [kg]" :value="old('weight')" />
</div>
{{-- 8 punktów krzywych - Q liczone z Qₙ, użytkownik podaje H i η --}}
<div>
<h2 class="font-title-sm text-title-sm text-tertiary mb-1">Parametry energetyczne (8 punktów)</h2>
<p class="font-body-sm text-body-sm text-on-surface-variant mb-2">
Wartości Q wyliczane automatycznie z Qₙ. Podaj H i η dla każdego punktu.
</p>
<table class="w-full text-left border border-outline-variant/40 rounded-lg overflow-hidden">
<thead class="bg-secondary/10">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-3 py-2 w-10">Lp.</th>
<th class="px-3 py-2 text-right">Q [m³/h]</th>
<th class="px-3 py-2">H [m]</th>
<th class="px-3 py-2">η [-]</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30">
@foreach ($factors as $i => $f)
<tr>
<td class="px-3 py-1.5 font-data-label text-on-surface-variant">{{ $i + 1 }}.</td>
<td class="px-3 py-1.5 text-right font-data-label" data-qcell>0,000</td>
<td class="px-3 py-1.5">
<input type="text" name="h[{{ $i }}]" value="{{ old('h.'.$i) }}" required
class="w-28 bg-surface-container-low border border-outline-variant px-2 py-1 rounded font-data-label text-on-surface">
</td>
<td class="px-3 py-1.5">
<input type="text" name="eta[{{ $i }}]" value="{{ old('eta.'.$i) }}" required
class="w-28 bg-surface-container-low border border-outline-variant px-2 py-1 rounded font-data-label text-on-surface">
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="flex items-center gap-2">
<button type="submit" class="bg-primary text-on-primary px-6 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">Zapisz</button>
<a href="{{ route('pumps.index') }}" class="px-5 py-2.5 rounded-full font-title-sm text-title-sm text-on-surface-variant hover:bg-surface-container transition-all">Anuluj</a>
</div>
</form>
</div>
<script>
(function () {
const factors = @json($factors);
const qn = document.getElementById('q_n');
const cells = document.querySelectorAll('[data-qcell]');
function recalc() {
const v = parseFloat((qn.value || '').replace(',', '.')) || 0;
cells.forEach((el, i) => { el.textContent = (factors[i] * v).toFixed(3).replace('.', ','); });
}
qn.addEventListener('input', recalc);
recalc();
})();
</script>
</x-app-layout>
@@ -0,0 +1,88 @@
<x-app-layout :active="'pumps.index'" title="SoftSPM 2 - Edycja pompy">
<div class="max-w-4xl space-y-stack-gap">
<div class="flex items-center gap-2">
<a href="{{ route('pumps.show', $pump) }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Wróć</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Edycja pompy: {{ $pump->PUP_NAME }}</h1>
</div>
@if ($errors->any())
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ $errors->first() }}</div>
@endif
<form method="POST" action="{{ route('pumps.update', $pump) }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding space-y-6">
@csrf
@method('PUT')
{{-- Dane podstawowe --}}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<x-well-field name="name" label="Typ pompy" :value="$pump->PUP_NAME" required class="sm:col-span-2" />
<x-well-select name="manufacturer_id" label="Producent" :options="$manufacturers" :selected="$pump->PUP_MCF_ID" required class="sm:col-span-2" />
<x-well-field name="_qn" label="Qₙ [m³/h]" :value="$qn" readonly />
<x-well-field name="length" label="Długość [mm]" :value="$pump->PUP_PUMP_LENGTH" />
<x-well-field name="diameter" label="Średnica [mm]" :value="$pump->PUP_DIAMETER" />
<x-well-field name="weight" label="Masa [kg]" :value="$pump->PUP_WEIGHT" />
</div>
{{-- Krzywe --}}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
{{-- Q-H --}}
<div>
<h2 class="font-title-sm text-title-sm text-tertiary mb-2">Wysokość podnoszenia</h2>
<table class="w-full text-left border border-outline-variant/40 rounded-lg overflow-hidden">
<thead class="bg-secondary/10">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-3 py-2 text-right">Q [m³/h]</th>
<th class="px-3 py-2">H [m]</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30">
@foreach ($h as $pt)
<tr>
<td class="px-3 py-1.5 text-right font-data-label text-on-surface-variant">{{ $pt->x }}</td>
<td class="px-3 py-1.5">
<input type="text" name="h[{{ $pt->id }}]" value="{{ $pt->y }}"
class="w-28 bg-surface-container-low border border-outline-variant px-2 py-1 rounded font-data-label text-on-surface">
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
{{-- Q-ETA --}}
<div>
<h2 class="font-title-sm text-title-sm text-tertiary mb-2">Sprawność</h2>
<table class="w-full text-left border border-outline-variant/40 rounded-lg overflow-hidden">
<thead class="bg-secondary/10">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-3 py-2 text-right">Q [m³/h]</th>
<th class="px-3 py-2">η [-]</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30">
@foreach ($eta as $pt)
<tr>
<td class="px-3 py-1.5 text-right font-data-label text-on-surface-variant">{{ $pt->x }}</td>
<td class="px-3 py-1.5">
<input type="text" name="eta[{{ $pt->id }}]" value="{{ $pt->y }}"
class="w-28 bg-surface-container-low border border-outline-variant px-2 py-1 rounded font-data-label text-on-surface">
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="flex items-center gap-2">
<button type="submit"
class="bg-primary text-on-primary px-6 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">
Zapisz zmiany
</button>
<a href="{{ route('pumps.show', $pump) }}" class="px-5 py-2.5 rounded-full font-title-sm text-title-sm text-on-surface-variant hover:bg-surface-container transition-all">Anuluj</a>
</div>
</form>
</div>
</x-app-layout>
@@ -0,0 +1,95 @@
<x-app-layout :active="'pumps.index'" title="SoftSPM 2 - Katalog pomp">
<div class="space-y-stack-gap">
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('status') }}</div>
@endif
<div class="flex items-end justify-between">
<div>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Katalog pomp</h1>
<p class="font-body-sm text-body-sm text-on-surface-variant mt-1">
Wyników: <span class="font-data-label">{{ $pumps->total() }}</span>
</p>
</div>
<a href="{{ route('pumps.create') }}"
class="bg-primary text-on-primary px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">
Dodaj pompę
</a>
</div>
{{-- Wyszukiwarka --}}
<form method="GET" action="{{ route('pumps.index') }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<div class="flex flex-wrap items-end gap-4">
<div class="space-y-1">
<label for="by" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Kryterium</label>
<select id="by" name="by" class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
<option value="name" {{ $by === 'name' ? 'selected' : '' }}>Typ pompy</option>
<option value="flow" {{ $by === 'flow' ? 'selected' : '' }}>Wydajność Q</option>
</select>
</div>
<div class="space-y-1">
<label for="q" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Szukaj</label>
<input id="q" name="q" type="text" value="{{ $q }}" placeholder="typ lub wartość Q"
class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
</div>
<div class="space-y-1">
<label for="mcf" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Producent</label>
<select id="mcf" name="mcf" class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
<option value="{{ $mcfAll }}" {{ (int) $mcf === $mcfAll ? 'selected' : '' }}>Wszyscy</option>
@foreach ($manufacturers as $id => $name)
<option value="{{ $id }}" {{ (int) $mcf === (int) $id ? 'selected' : '' }}>{{ $name }}</option>
@endforeach
</select>
</div>
<button type="submit"
class="bg-secondary text-on-secondary px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:opacity-90 transition-all active:scale-95">
Szukaj
</button>
</div>
</form>
{{-- Tabela --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<table class="w-full text-left">
<thead class="bg-secondary/10 border-b border-outline-variant/40">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-4 py-3 w-12">Lp.</th>
<x-sortable-th column="name">Typ pompy</x-sortable-th>
<x-sortable-th column="flow" align="right">Q<sub>n</sub> [m³/h]</x-sortable-th>
<x-sortable-th column="hp" align="right">H<sub>p</sub> [m]</x-sortable-th>
<x-sortable-th column="mcf">Producent</x-sortable-th>
<th class="px-4 py-3 text-right">Akcje</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30 font-body-md text-body-md">
@forelse ($pumps as $pump)
<tr class="hover:bg-surface-container transition-colors">
<td class="px-4 py-3 font-data-label text-on-surface-variant">{{ $pumps->firstItem() + $loop->index }}.</td>
<td class="px-4 py-3 font-semibold text-on-surface">
<a href="{{ route('pumps.show', $pump) }}" class="hover:text-primary">{{ $pump->PUP_NAME }}</a>
</td>
<td class="px-4 py-3 text-right font-data-label">{{ number_format(round((float) $pump->PUP_Q_N, 3), 3, ',', ' ') }}</td>
<td class="px-4 py-3 text-right font-data-label">{{ $pump->PUP_H_P }}</td>
<td class="px-4 py-3">{{ $pump->manufacturer?->MCF_NAME }}</td>
<td class="px-4 py-3 text-right whitespace-nowrap">
<a href="{{ route('pumps.show', $pump) }}" class="font-body-sm text-body-sm text-secondary hover:underline">Szczegóły</a>
<span class="text-outline-variant mx-1">·</span>
<a href="{{ route('pumps.edit', $pump) }}" class="font-body-sm text-body-sm text-secondary hover:underline">Edytuj</a>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-4 py-8 text-center font-body-md text-body-md text-on-surface-variant">
Brak pomp dla wybranych kryteriów.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div>{{ $pumps->links() }}</div>
</div>
</x-app-layout>
@@ -0,0 +1,152 @@
<x-app-layout :active="'pumps.index'" title="SoftSPM 2 - {{ $pump->PUP_NAME }}">
@php
$fmt = fn ($v, $d = 3) => $v === null ? '-' : number_format(round((float) $v, $d), $d, ',', ' ');
@endphp
<div class="space-y-stack-gap">
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('status') }}</div>
@endif
{{-- Nagłówek + akcje --}}
<div class="flex flex-wrap items-center justify-between gap-3">
<div class="flex items-center gap-2">
<a href="{{ route('pumps.index') }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Katalog pomp</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">{{ $pump->PUP_NAME }}</h1>
</div>
<div class="flex items-center gap-2">
<a href="{{ route('pumps.edit', $pump) }}"
class="bg-secondary text-on-secondary px-4 py-2 rounded-full font-title-sm text-title-sm hover:opacity-90 transition-all active:scale-95">Edytuj</a>
<x-confirm-delete :action="route('pumps.destroy', $pump)"
message="Czy na pewno usunąć pompę „{{ $pump->PUP_NAME }}”? Tej operacji nie można cofnąć." />
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-stack-gap">
{{-- LEWA: dane techniczne --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-3">Dane techniczne pompy</h2>
<dl class="grid grid-cols-2 gap-x-4 gap-y-2 font-body-md text-body-md">
<dt class="text-on-surface-variant">Typ pompy</dt><dd class="font-semibold">{{ $pump->PUP_NAME }}</dd>
<dt class="text-on-surface-variant">Producent</dt><dd>{{ $pump->manufacturer?->MCF_NAME ?? '-' }}</dd>
<dt class="col-span-2 border-t border-outline-variant/30 mt-1 pt-1"></dt>
<dt class="text-on-surface-variant">Q<sub>n</sub> [m³/h]</dt><dd class="font-data-label">{{ $fmt($pump->PUP_Q_N) }}</dd>
<dt class="text-on-surface-variant">Q<sub>min</sub> [m³/h]</dt><dd class="font-data-label">{{ $fmt($pump->PUP_Q_MIN) }}</dd>
<dt class="text-on-surface-variant">Q<sub>max</sub> [m³/h]</dt><dd class="font-data-label">{{ $fmt($pump->PUP_Q_MAX) }}</dd>
<dt class="text-on-surface-variant">H<sub>p</sub> [m]</dt><dd class="font-data-label">{{ $fmt($pump->PUP_H_P, 2) }}</dd>
<dt class="text-on-surface-variant">H<sub>min</sub> [m]</dt><dd class="font-data-label">{{ $fmt($pump->PUP_H_MIN, 2) }}</dd>
<dt class="text-on-surface-variant">H<sub>max</sub> [m]</dt><dd class="font-data-label">{{ $fmt($pump->PUP_H_MAX, 2) }}</dd>
<dt class="text-on-surface-variant">η<sub>max</sub> [-]</dt><dd class="font-data-label">{{ $fmt($pump->PUP_ETA_MAX, 2) }}</dd>
<dt class="col-span-2 border-t border-outline-variant/30 mt-1 pt-1"></dt>
<dt class="text-on-surface-variant">Długość [mm]</dt><dd class="font-data-label">{{ $fmt($pump->PUP_PUMP_LENGTH, 2) }}</dd>
<dt class="text-on-surface-variant">Średnica [mm]</dt><dd class="font-data-label">{{ $fmt($pump->PUP_DIAMETER, 2) }}</dd>
<dt class="text-on-surface-variant">Masa [kg]</dt><dd class="font-data-label">{{ $fmt($pump->PUP_WEIGHT, 2) }}</dd>
</dl>
</div>
{{-- PRAWA: wykresy --}}
<div class="space-y-stack-gap">
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-2">Wysokość podnoszenia &nbsp;<span class="font-body-sm text-on-surface-variant">H = f(Q)</span></h2>
@if ($hDrawable)
<x-pump-curve :plot="$hPlot" id="hchart" :interactive="true" :initial-q="$q"
:eta-coeffs="data_get($etaPlot, 'coeffs', [])"
q-input="#q" h-output="#rv-h" eta-output="#rv-eta"
x-label="Q [m³/h]" y-label="H [m]" />
@else
<p class="font-body-sm text-body-sm text-on-surface-variant py-6 text-center">Brak parametrów energetycznych dla pompy.</p>
@endif
</div>
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-2">Sprawność &nbsp;<span class="font-body-sm text-on-surface-variant">η = f(Q)</span></h2>
@if ($etaDrawable)
<x-pump-curve :plot="$etaPlot" x-label="Q [m³/h]" y-label="η [-]" />
@else
<p class="font-body-sm text-body-sm text-on-surface-variant py-6 text-center">Brak parametrów energetycznych dla pompy.</p>
@endif
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-stack-gap">
{{-- Parametry energetyczne (punkty krzywych) --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="px-card-inner-padding pt-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-2">Parametry energetyczne</h2>
</div>
<table class="w-full text-left">
<thead class="bg-secondary/10 border-y border-outline-variant/40">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-4 py-2 w-10">Lp.</th>
<th class="px-4 py-2 text-right">Q [m³/h]</th>
<th class="px-4 py-2 text-right">H [m]</th>
<th class="px-4 py-2 text-right">η [-]</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30 font-data-label">
@foreach ($h as $i => $pt)
<tr>
<td class="px-4 py-1.5 text-on-surface-variant">{{ $i + 1 }}.</td>
<td class="px-4 py-1.5 text-right">{{ $pt->x }}</td>
<td class="px-4 py-1.5 text-right">{{ $pt->y }}</td>
<td class="px-4 py-1.5 text-right">{{ $eta[$i]->y ?? '-' }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
{{-- Przelicz dla zadanego Q + lista silników --}}
<div class="space-y-stack-gap">
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-3">Parametry energetyczne na zadane Q</h2>
<form method="GET" action="{{ route('pumps.show', $pump) }}" class="flex flex-wrap items-end gap-4">
<div class="space-y-1">
<label for="q" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Dla Q [m³/h]</label>
<input id="q" name="q" type="text" value="{{ number_format($q, 3, ',', '') }}"
class="w-32 bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
</div>
<button type="submit" class="bg-secondary text-on-secondary px-4 py-2 rounded-full font-title-sm text-title-sm hover:opacity-90 active:scale-95">Przelicz</button>
@if ($outOfRange)
<span class="font-body-sm text-body-sm text-error">poza zakresem</span>
@endif
</form>
<dl class="grid grid-cols-2 gap-2 mt-3 font-body-md text-body-md">
<dt class="text-on-surface-variant">Wartość H [m]</dt><dd id="rv-h" class="font-data-label font-semibold">{{ $hAtQ === null ? '-' : number_format(round($hAtQ, 2), 2, ',', ' ') }}</dd>
<dt class="text-on-surface-variant">Wartość η [-]</dt><dd id="rv-eta" class="font-data-label font-semibold">{{ $etaAtQ === null ? '-' : number_format(round($etaAtQ, 2), 2, ',', ' ') }}</dd>
</dl>
</div>
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="px-card-inner-padding pt-card-inner-padding">
<h2 class="font-title-sm text-title-sm text-tertiary mb-2">Lista silników dla pompy</h2>
</div>
<table class="w-full text-left">
<thead class="bg-secondary/10 border-y border-outline-variant/40">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-4 py-2">Typ silnika</th>
<th class="px-4 py-2 text-right">Napięcie [V]</th>
<th class="px-4 py-2 text-right">Moc [kW]</th>
<th class="px-4 py-2">Producent</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30 font-body-md text-body-md">
@forelse ($relatedEngines as $e)
<tr>
<td class="px-4 py-1.5">{{ $e->ENG_NAME }}</td>
<td class="px-4 py-1.5 text-right font-data-label">{{ $e->ENG_VOLTAGE }}</td>
<td class="px-4 py-1.5 text-right font-data-label">{{ $e->ENG_POWER }}</td>
<td class="px-4 py-1.5">{{ $e->MCF_NAME }}</td>
</tr>
@empty
<tr><td colspan="4" class="px-4 py-4 text-center font-body-sm text-on-surface-variant">Brak powiązanych silników.</td></tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</x-app-layout>
@@ -0,0 +1,295 @@
@props([
'active' => null, // nazwa trasy aktywnej pozycji (np. 'wells.index')
'section' => null, // klucz sekcji (gdy strona nie jest pozycją menu, np. landing sekcji)
'title' => 'SoftSPM 2',
])
@php
$sections = config('navigation');
// Ustal aktywną sekcję: jawny prop > sekcja zawierająca aktywną pozycję > pierwsza.
$activeSectionKey = $section;
if (! $activeSectionKey && $active) {
foreach ($sections as $s) {
foreach ($s['items'] as $it) {
if (($it['route'] ?? null) === $active) {
$activeSectionKey = $s['key'];
break 2;
}
}
}
}
$activeSectionKey = $activeSectionKey ?? ($sections[0]['key'] ?? null);
$activeSection = collect($sections)->firstWhere('key', $activeSectionKey);
// Motyw z cookie (serwer ustawia klasę przy renderze - brak migotania).
// Czytamy SUROWE $_COOKIE - cookie 'theme' ustawia JS bez szyfrowania, więc
// request()->cookie() (przez EncryptCookies) by go nie odczytał.
$theme = ($_COOKIE['theme'] ?? null) === 'dark' ? 'dark' : 'light';
// Inicjały użytkownika do awatara w menu.
$uname = auth()->user()?->USR_USERNAME ?? '?';
$unameParts = preg_split('/[^\p{L}\p{N}]+/u', $uname, -1, PREG_SPLIT_NO_EMPTY) ?: [$uname];
$initials = mb_strtoupper(count($unameParts) >= 2
? mb_substr($unameParts[0], 0, 1).mb_substr($unameParts[1], 0, 1)
: mb_substr($uname, 0, 2));
$languages = ['pl' => 'PL', 'en' => 'EN', 'de' => 'DE'];
@endphp
<!DOCTYPE html>
<html class="{{ $theme }}" lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ $title }}</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-surface text-on-surface min-h-screen">
{{-- ===== Górny pasek: SEKCJE (zakres) ===== --}}
<header class="sticky top-0 z-30 bg-surface-container-lowest border-b border-outline-variant/40">
<div class="flex items-center justify-between h-16 px-gutter">
{{-- Logo + sekcje razem, wyrównane do lewej --}}
<div class="flex items-center gap-10 min-w-0">
<a href="{{ route('dashboard') }}" class="shrink-0">
<img src="{{ asset('images/logo.png') }}" alt="SoftSPM 2" class="h-9 w-auto">
</a>
<nav class="hidden md:flex items-center gap-6 font-body-md text-body-md overflow-x-auto">
@foreach ($sections as $s)
@php
$landing = isset($s['route']) && Route::has($s['route'])
? route($s['route'])
: route('section', $s['key']);
@endphp
<a href="{{ $landing }}"
class="whitespace-nowrap py-1 border-b-2 transition-colors hover:text-primary
{{ $activeSectionKey === $s['key'] ? 'text-primary font-semibold border-primary' : 'text-on-surface border-transparent' }}">
{{ $s['label'] }}
</a>
@endforeach
</nav>
</div>
<div class="flex items-center gap-2 shrink-0">
{{-- Przełącznik motywu jasny/ciemny --}}
<button type="button" id="theme-toggle" title="Przełącz motyw jasny/ciemny" aria-label="Przełącz motyw jasny/ciemny"
class="p-2 rounded-full text-on-surface-variant hover:bg-surface-container hover:text-primary transition-colors">
{{-- W jasnym pokazuje księżyc (przejście w ciemny) --}}
<svg class="w-5 h-5 dark:hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9z"/>
</svg>
{{-- W ciemnym pokazuje słońce (przejście w jasny) --}}
<svg class="w-5 h-5 hidden dark:block" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="4"/>
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/>
</svg>
</button>
{{-- Menu użytkownika: awatar -> rozwijane (język + wyloguj) --}}
<div class="relative" id="user-menu">
<button type="button" id="user-menu-button" aria-haspopup="true" aria-expanded="false"
class="flex items-center gap-2 rounded-full hover:opacity-90 transition-opacity">
<span class="grid place-items-center w-9 h-9 rounded-full bg-primary text-on-primary font-data-label text-data-label">{{ $initials }}</span>
</button>
<div id="user-menu-panel"
class="hidden absolute right-0 mt-2 w-60 bg-surface-container-lowest border border-outline-variant/40 rounded-xl shadow-card py-2 z-40">
<div class="px-4 py-2 border-b border-outline-variant/30">
<p class="font-body-sm text-body-sm text-on-surface-variant">Zalogowano jako</p>
<p class="font-data-label text-data-label text-on-surface truncate">{{ $uname }}</p>
</div>
<div class="px-4 py-3">
<p class="font-data-label text-data-label text-on-surface-variant/70 uppercase tracking-wider mb-2">Język</p>
<div class="flex gap-1.5">
@foreach ($languages as $code => $label)
<a href="{{ route('locale.set', $code) }}"
class="px-3 py-1 rounded-md font-data-label text-data-label transition-colors
{{ app()->getLocale() === $code ? 'bg-secondary-container text-on-secondary-container font-semibold' : 'text-on-surface-variant hover:bg-surface-container' }}">
{{ $label }}
</a>
@endforeach
</div>
</div>
<div class="border-t border-outline-variant/30 mt-1 pt-1">
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit"
class="w-full text-left px-4 py-2 font-body-md text-body-md text-on-surface hover:bg-surface-container transition-colors">
Wyloguj
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</header>
<div class="flex min-h-[calc(100vh-4rem)]">
{{-- ===== Lewy sidebar: POZYCJE aktywnej sekcji ===== --}}
<aside class="hidden lg:block w-72 shrink-0 bg-surface-container-lowest border-r border-outline-variant/40 p-gutter">
<p class="font-data-label text-data-label text-on-surface-variant uppercase tracking-wider mb-4">
{{ $activeSection['label'] ?? 'Nawigacja' }}
</p>
<nav class="space-y-1">
@forelse ($activeSection['items'] ?? [] as $it)
@php
$r = $it['route'] ?? null;
$url = ($r && Route::has($r)) ? route($r) : '#';
$isActive = $r && $r === $active;
@endphp
<a href="{{ $url }}"
class="flex items-center gap-3 px-4 py-3 rounded-full font-title-sm text-title-sm transition-all
{{ $isActive ? 'bg-secondary-container text-on-secondary-container font-semibold' : 'text-on-surface-variant hover:bg-surface-variant/50' }}">
<x-icon :name="$it['icon'] ?? ''" class="w-5 h-5 shrink-0" />
<span>{{ $it['label'] }}@if (! empty($it['append_department']) && session('department')) - {{ session('department') }}@endif</span>
</a>
@empty
<p class="px-3 py-2 font-body-sm text-body-sm text-on-surface-variant">Moduł w przygotowaniu.</p>
@endforelse
</nav>
</aside>
{{-- ===== Treść ===== --}}
<main class="flex-1 p-gutter">
{{ $slot }}
</main>
</div>
<footer class="border-t border-outline-variant/30 px-gutter py-3 text-center font-body-sm text-body-sm text-on-surface-variant">
© SoftSPM Przemysław Zatorski · v.2.0
</footer>
{{-- ===== Globalny preloader (pokazywany gdy nawigacja/wyszukiwanie trwa dłużej) ===== --}}
<div id="page-loader" class="fixed inset-0 z-[70] hidden items-center justify-center bg-on-surface/30 backdrop-blur-sm">
<div class="flex flex-col items-center gap-3 bg-surface-container-lowest/95 px-7 py-6 rounded-xl shadow-card border border-outline-variant/40">
<div class="w-12 h-12 rounded-full border-4 border-secondary/25 border-t-secondary animate-spin"></div>
<span class="font-data-label text-data-label text-on-surface-variant tracking-wide">Ładowanie...</span>
</div>
</div>
{{-- ===== Wspólny modal potwierdzenia (wyśrodkowany) ===== --}}
<div id="confirm-modal" class="fixed inset-0 z-50 hidden items-center justify-center p-4">
<div class="absolute inset-0 bg-on-surface/40 backdrop-blur-sm" data-confirm-cancel></div>
<div class="relative w-full max-w-md bg-surface-container-lowest rounded-xl shadow-card border border-outline-variant/40 p-6 space-y-4">
<h3 id="confirm-modal-title" class="font-headline-md text-headline-md text-on-surface">Potwierdzenie</h3>
<p id="confirm-modal-message" class="font-body-md text-body-md text-on-surface-variant">Czy na pewno?</p>
<div class="flex justify-end gap-2 pt-2">
<button type="button" data-confirm-cancel
class="px-5 py-2.5 rounded-full font-title-sm text-title-sm text-on-surface-variant hover:bg-surface-container transition-all">Anuluj</button>
<button type="button" id="confirm-modal-ok"
class="bg-error text-on-error px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:opacity-90 active:scale-95 transition-all">Usuń</button>
</div>
</div>
</div>
<script>
(function () {
const modal = document.getElementById('confirm-modal');
if (!modal) return;
const titleEl = document.getElementById('confirm-modal-title');
const msgEl = document.getElementById('confirm-modal-message');
const okBtn = document.getElementById('confirm-modal-ok');
let pendingForm = null;
function open(form) {
pendingForm = form;
titleEl.textContent = form.dataset.confirmTitle || 'Potwierdzenie usunięcia';
msgEl.textContent = form.dataset.confirmMessage || 'Czy na pewno?';
okBtn.textContent = form.dataset.confirmButton || 'Usuń';
modal.classList.remove('hidden');
modal.classList.add('flex');
}
function close() {
modal.classList.add('hidden');
modal.classList.remove('flex');
pendingForm = null;
}
document.addEventListener('click', function (e) {
const trigger = e.target.closest('[data-confirm-trigger]');
if (trigger) { e.preventDefault(); open(trigger.closest('form')); return; }
if (e.target.closest('[data-confirm-cancel]')) { close(); }
});
okBtn.addEventListener('click', function () { if (pendingForm) pendingForm.submit(); });
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') close(); });
})();
</script>
{{-- Globalny preloader: pojawia się tylko gdy odpowiedź serwera trwa dłużej niż DELAY --}}
<script>
(function () {
const loader = document.getElementById('page-loader');
if (!loader) return;
const DELAY = 250; // ms - pokaż dopiero gdy ładowanie się przeciąga
let timer = null;
function show() { loader.classList.remove('hidden'); loader.classList.add('flex'); }
function hide() { clearTimeout(timer); loader.classList.add('hidden'); loader.classList.remove('flex'); }
function schedule() { clearTimeout(timer); timer = setTimeout(show, DELAY); }
// Wysłanie formularza (wyszukiwarki, filtry, zapisy) - oprócz otwieranych w nowej karcie.
document.addEventListener('submit', function (e) {
const form = e.target;
if (form.matches && form.matches('form') && form.target !== '_blank') schedule();
}, true);
// Nawigacja kliknięciem w wewnętrzny link (paginacja, menu, akcje).
document.addEventListener('click', function (e) {
const a = e.target.closest && e.target.closest('a[href]');
if (!a) return;
const href = a.getAttribute('href');
if (!href || href.charAt(0) === '#' || a.target === '_blank' || a.hasAttribute('download')) return;
if (a.origin && a.origin !== window.location.origin) return; // tylko ten sam serwer
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
schedule();
});
// Powrót z cache przeglądarki (przycisk Wstecz) - ukryj ewentualny preloader.
window.addEventListener('pageshow', hide);
})();
</script>
{{-- Motyw (jasny/ciemny) + menu użytkownika --}}
<script>
(function () {
// ===== Motyw - zapis w cookie na ~400 dni, odświeżany przy każdej wizycie =====
const COOKIE_MAX_AGE = 60 * 60 * 24 * 400; // sekundy (~400 dni, maksimum przeglądarek)
function applyTheme(t) {
const r = document.documentElement;
r.classList.toggle('dark', t === 'dark');
r.classList.toggle('light', t !== 'dark');
document.cookie = 'theme=' + t + ';path=/;max-age=' + COOKIE_MAX_AGE + ';samesite=lax';
}
// Odśwież okno ważności cookie (utrzymanie wyboru "na zawsze" mimo limitu przeglądarki).
applyTheme(document.documentElement.classList.contains('dark') ? 'dark' : 'light');
const toggle = document.getElementById('theme-toggle');
if (toggle) toggle.addEventListener('click', function () {
applyTheme(document.documentElement.classList.contains('dark') ? 'light' : 'dark');
});
// ===== Menu użytkownika (rozwijane) =====
const btn = document.getElementById('user-menu-button');
const panel = document.getElementById('user-menu-panel');
if (btn && panel) {
function closeMenu() { panel.classList.add('hidden'); btn.setAttribute('aria-expanded', 'false'); }
btn.addEventListener('click', function (e) {
e.stopPropagation();
const willOpen = panel.classList.contains('hidden');
panel.classList.toggle('hidden');
btn.setAttribute('aria-expanded', String(willOpen));
});
document.addEventListener('click', function (e) {
if (!panel.contains(e.target) && !btn.contains(e.target)) closeMenu();
});
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeMenu(); });
}
})();
</script>
</body>
</html>
+24
View File
@@ -0,0 +1,24 @@
@props([
'variant' => 'secondary', // primary | secondary | ghost | danger
'as' => 'button', // 'button' | 'a'
'disabled' => false,
])
@php
// Jednolity styl przycisków dla CAŁEGO projektu. Zawsze pigułka px-5 py-2.5.
$base = 'inline-flex items-center justify-center px-5 py-2.5 rounded-full font-title-sm text-title-sm transition-all';
$variants = [
'primary' => 'bg-primary text-on-primary hover:bg-primary-container active:scale-95 shadow-card',
'secondary' => 'bg-secondary text-on-secondary hover:opacity-90 active:scale-95',
'ghost' => 'text-on-surface-variant hover:bg-surface-container',
'danger' => 'bg-error text-on-error hover:opacity-90 active:scale-95',
];
$v = $variants[$variant] ?? $variants['secondary'];
$cls = $base.' '.$v.($disabled ? ' opacity-50 cursor-not-allowed pointer-events-none' : '');
@endphp
@if ($as === 'a')
<a {{ $attributes->merge(['class' => $cls]) }}>{{ $slot }}</a>
@else
<button @disabled($disabled) {{ $attributes->merge(['type' => 'button', 'class' => $cls]) }}>{{ $slot }}</button>
@endif
@@ -0,0 +1,20 @@
@props([
'action',
'message' => 'Czy na pewno usunąć ten obiekt? Tej operacji nie można cofnąć.',
'title' => 'Potwierdzenie usunięcia',
'button' => 'Usuń',
'label' => 'Usuń',
])
{{-- Formularz usuwania - przycisk otwiera wspólny modal (patrz app-layout). --}}
<form method="POST" action="{{ $action }}"
data-confirm-message="{{ $message }}"
data-confirm-title="{{ $title }}"
data-confirm-button="{{ $button }}">
@csrf
@method('DELETE')
<button type="button" data-confirm-trigger
class="bg-error text-on-error px-4 py-2 rounded-full font-title-sm text-title-sm hover:opacity-90 transition-all active:scale-95">
{{ $label }}
</button>
</form>
@@ -0,0 +1,35 @@
@props([
'points', // kolekcja {x, y}
'xLabel' => 'Q',
'yLabel' => 'Y',
'color' => '#006876',
])
@php
$w = 340; $h = 210; $padL = 40; $padR = 12; $padT = 12; $padB = 26;
$plotW = $w - $padL - $padR; $plotH = $h - $padT - $padB;
$xMax = max(0.0001, (float) $points->max('x'));
$yMax = max(0.0001, (float) $points->max('y'));
$sx = fn ($x) => $padL + ($x / $xMax) * $plotW;
$sy = fn ($y) => $padT + $plotH - ($y / $yMax) * $plotH;
$poly = $points->map(fn ($p) => round($sx($p->x), 1).','.round($sy($p->y), 1))->implode(' ');
@endphp
<svg viewBox="0 0 {{ $w }} {{ $h }}" class="w-full h-auto" xmlns="http://www.w3.org/2000/svg">
{{-- osie --}}
<line x1="{{ $padL }}" y1="{{ $padT }}" x2="{{ $padL }}" y2="{{ $padT + $plotH }}" stroke="#cbd5d6" stroke-width="1"/>
<line x1="{{ $padL }}" y1="{{ $padT + $plotH }}" x2="{{ $padL + $plotW }}" y2="{{ $padT + $plotH }}" stroke="#cbd5d6" stroke-width="1"/>
{{-- krzywa --}}
<polyline points="{{ $poly }}" fill="none" stroke="{{ $color }}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
{{-- punkty --}}
@foreach ($points as $p)
<circle cx="{{ round($sx($p->x), 1) }}" cy="{{ round($sy($p->y), 1) }}" r="3" fill="{{ $color }}"/>
@endforeach
{{-- skrajne wartości osi --}}
<text x="{{ $padL - 4 }}" y="{{ $padT + 6 }}" font-size="8" fill="#888" text-anchor="end">{{ rtrim(rtrim(number_format($yMax, 1, '.', ''), '0'), '.') }}</text>
<text x="{{ $padL - 4 }}" y="{{ $padT + $plotH }}" font-size="8" fill="#888" text-anchor="end">0</text>
<text x="{{ $padL + $plotW }}" y="{{ $padT + $plotH + 12 }}" font-size="8" fill="#888" text-anchor="end">{{ rtrim(rtrim(number_format($xMax, 2, '.', ''), '0'), '.') }}</text>
{{-- etykiety osi --}}
<text x="{{ $padL }}" y="{{ $padT - 3 }}" font-size="9" fill="#555">{{ $yLabel }}</text>
<text x="{{ $padL + $plotW }}" y="{{ $h - 4 }}" font-size="9" fill="#555" text-anchor="end">{{ $xLabel }}</text>
</svg>
@@ -0,0 +1,55 @@
@props([
'eta', // sprawność η (0..~0.7) lub null
'ry', // granica czerwony/żółty (η)
'yg', // granica żółty/zielony (η)
'zone' => null, // 'green'|'yellow'|'red' (kolor opisu)
])
@php
// Skala 0..0.7 jak legacy. Pozycja znormalizowana: 0=lewo (wysoka η, zielona), 1=prawo.
$clamp = fn ($v) => max(0.0, min(1.0, $v));
$pYG = $clamp(1 - ($yg / 0.7)); // granica zielony/żółty
$pRY = $clamp(1 - ($ry / 0.7)); // granica żółty/czerwony
$pYG = min($pYG, $pRY);
$pEta = $eta === null ? null : $clamp(1 - ($eta / 0.7));
// Wyraźne strefy z krótkim przejściem (jak oryginał: green | yellow | red).
$t = 0.03;
$stops = [];
$prev = 0.0;
$add = function ($off, $color) use (&$stops, &$prev, $clamp) {
$off = max($prev, $clamp($off)); // offsety rosnące
$stops[] = [round($off * 100, 2), $color];
$prev = $off;
};
$add(0, '#1f9d57');
$add($pYG - $t, '#1f9d57');
$add($pYG + $t, '#f4c20d');
$add($pRY - $t, '#f4c20d');
$add($pRY + $t, '#d32f2f');
$add(1, '#d32f2f');
$id = 'eg-'.substr(md5(($eta ?? 0).$ry.$yg), 0, 6);
$pct = fn ($v) => round($v * 100, 2);
$zoneColor = ['green' => '#1f9d57', 'yellow' => '#f4c20d', 'red' => '#d32f2f'][$zone] ?? '#5b6566';
@endphp
@if ($eta === null)
<div class="h-10 rounded bg-surface-container grid place-items-center font-body-sm text-body-sm text-on-surface-variant">
Brak oceny
</div>
@else
<svg viewBox="0 0 100 16" class="w-full h-12" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="{{ $id }}" x1="0" y1="0" x2="1" y2="0">
@foreach ($stops as [$off, $color])
<stop offset="{{ $off }}%" stop-color="{{ $color }}"/>
@endforeach
</linearGradient>
</defs>
<rect x="0" y="3" width="100" height="10" rx="1.5" fill="url(#{{ $id }})"/>
{{-- Znacznik (dzwon) - pozycja wg η --}}
<line x1="{{ $pct($pEta) }}" y1="0.5" x2="{{ $pct($pEta) }}" y2="15.5" stroke="#101415" stroke-width="0.9"/>
<line x1="{{ $pct($pEta) }}" y1="0.5" x2="{{ $pct($pEta) }}" y2="15.5" stroke="#ffffff" stroke-width="0.3"/>
</svg>
@endif
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class="light" lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ $title ?? 'SoftSPM 2' }}</title>
{{-- Air-gap: brak CDN. Assety budowane lokalnie przez Vite, fonty self-hosted. --}}
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="min-h-screen bg-surface text-on-surface antialiased">
{{ $slot }}
</body>
</html>
+31
View File
@@ -0,0 +1,31 @@
@props(['name' => '', 'class' => 'w-5 h-5'])
@php
// Ikony jako wbudowane SVG (Lucide-style) - działają offline, bez fontu.
// Klucze = nazwy 'icon' z config/navigation.php.
$paths = [
'home' => '<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/>',
'map' => '<path d="M3 6 9 3l6 3 6-3v15l-6 3-6-3-6 3z"/><path d="M9 3v15"/><path d="M15 6v15"/>',
'inventory_2' => '<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>',
'monitoring' => '<path d="M3 12h4l3 8 4-16 3 8h4"/>',
'engineering' => '<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>',
'biotech' => '<path d="M10 2v6.3a1 1 0 0 1-.4.8l-3.6 2.6A4 4 0 0 0 8.5 19h7a4 4 0 0 0 2.5-7.3l-3.6-2.6a1 1 0 0 1-.4-.8V2"/><path d="M8.5 2h7"/><path d="M7 16h10"/>',
'build' => '<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>',
'settings' => '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>',
'menu_book' => '<path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/>',
'water_drop' => '<path d="M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5C4 11.1 3 13 3 15a7 7 0 0 0 7 7z"/>',
'bolt' => '<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>',
'precision_manufacturing' => '<path d="M2 20a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8l-7 5V8l-7 5V4a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/><path d="M7 18h.01"/><path d="M12 18h.01"/><path d="M17 18h.01"/>',
'checklist' => '<path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/>',
'cable' => '<path d="M5 3v4"/><path d="M3 5h4"/><path d="M19 17v4"/><path d="M17 19h4"/><path d="M5 7a7 7 0 0 0 7 7 7 7 0 0 1 7 7"/>',
'plumbing' => '<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>',
'speed' => '<path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/>',
];
$svg = $paths[$name] ?? '<circle cx="12" cy="12" r="2"/>';
@endphp
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
{{ $attributes->merge(['class' => $class]) }}>
{!! $svg !!}
</svg>
@@ -0,0 +1,96 @@
@props([
'power' => null, // ['pe','pod','paz','psl','psa','pp2','pst','pgh','sum'] lub null
'on' => false,
'time' => null, // czas pomiaru
])
@php
// Schemat układu pompowego (zastępuje statyczny rozklad_mocy.png). Bez strzałek -
// rozkład mocy opisany legendą pod rysunkiem (każda składowa = inny kolor).
$f = fn ($v) => $v === null ? '-' : number_format(round((float) $v, 2), 2, ',', ' ');
$pumpColor = $on ? '#16a34a' : '#9aa6a8';
$lab = '#2f3e45';
// Legenda: [kolor (unikalny), opis, klucz wartości].
$legend = [
['#ff7a1a', 'Moc pobrana - kabel przyłączeniowy', 'pe'],
['#d32f2f', 'Moc na wyjściu pompy (agregatu)', 'pod'],
['#2563eb', 'Podnoszenie stałe (do zwierciadła statycznego)', 'pst'],
['#0891b2', 'Geo-hydraulika (na filtrach studni)', 'pgh'],
['#7c3aed', 'Odpływ (za zasuwą dławiącą)', 'pp2'],
['#f59e0b', 'Straty: agregat + zasilanie', 'paz'],
['#65a30d', 'Straty liniowe (rurociąg tłoczny)', 'psl'],
['#db2777', 'Straty w armaturze (zawór)', 'psa'],
];
@endphp
@if ($power === null)
<div class="py-10 text-center font-body-sm text-body-sm text-on-surface-variant">Brak danych pomiarowych do bilansu mocy.</div>
@else
<svg viewBox="110 0 350 580" class="w-full h-auto max-h-[30rem] mx-auto block" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="ps-ground" width="8" height="8" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="8" stroke="#b9a58c" stroke-width="2"/>
</pattern>
</defs>
{{-- ===== Instalacja powierzchniowa ===== --}}
<rect x="216" y="90" width="8" height="56" fill="#7aa7c7"/>
<path d="M220 92 H360" stroke="#7aa7c7" stroke-width="8" fill="none"/>
{{-- zawór dławiący --}}
<polygon points="248,82 248,102 272,82 272,102" fill="#e0e6ea" stroke="#3b3b3b" stroke-width="1.2"/>
<text x="244" y="80" font-size="8" fill="{{ $lab }}" text-anchor="end">zawór</text>
{{-- manometry + Q --}}
<circle cx="234" cy="68" r="7" fill="#fff" stroke="#3b3b3b" stroke-width="1.3"/><text x="234" y="57" font-size="9" fill="{{ $lab }}" text-anchor="middle">p₁</text>
<circle cx="296" cy="68" r="7" fill="#fff" stroke="#3b3b3b" stroke-width="1.3"/><text x="296" y="57" font-size="9" fill="{{ $lab }}" text-anchor="middle">p₂</text>
<rect x="316" y="80" width="16" height="14" rx="2" fill="#fff" stroke="#3b3b3b" stroke-width="1.3"/><text x="324" y="91" font-size="8" font-weight="700" fill="#3b3b3b" text-anchor="middle">Q</text>
{{-- głowica/silnik nawierzchniowy + skrzynka kabla --}}
<rect x="198" y="120" width="56" height="22" rx="2" fill="#cfd8dd" stroke="#5b6566"/>
<rect x="198" y="104" width="18" height="16" rx="2" fill="#37424a"/>
<text x="194" y="100" font-size="8" fill="{{ $lab }}" text-anchor="end">kabel</text>
{{-- ===== Grunt + studnia ===== --}}
<rect x="0" y="175" width="460" height="14" fill="url(#ps-ground)"/>
<line x1="0" y1="175" x2="460" y2="175" stroke="#8f706c" stroke-width="1.5"/>
<line x1="196" y1="175" x2="196" y2="540" stroke="#5b6566" stroke-width="3"/>
<line x1="246" y1="175" x2="246" y2="540" stroke="#5b6566" stroke-width="3"/>
<line x1="196" y1="540" x2="246" y2="540" stroke="#5b6566" stroke-width="3"/>
<rect x="198" y="345" width="48" height="195" fill="#cfeaf5" opacity="0.55"/>
{{-- zwierciadła --}}
<line x1="196" y1="315" x2="246" y2="315" stroke="#1565c0" stroke-width="1.3" stroke-dasharray="4 3"/>
<text x="252" y="318" font-size="9" fill="{{ $lab }}">H<tspan baseline-shift="sub" font-size="6.5">st</tspan></text>
<line x1="196" y1="345" x2="246" y2="345" stroke="#1565c0" stroke-width="1.8"/>
<text x="252" y="348" font-size="9" fill="{{ $lab }}">H<tspan baseline-shift="sub" font-size="6.5">d</tspan></text>
{{-- rurociąg pionowy + kabel zasilający --}}
<rect x="216" y="142" width="8" height="332" fill="#7aa7c7"/>
<line x1="208" y1="142" x2="208" y2="470" stroke="#37424a" stroke-width="2.5"/>
{{-- filtry studni (perforacja) --}}
@for ($i = 0; $i < 7; $i++)
<line x1="188" y1="{{ 474 + $i * 9 }}" x2="196" y2="{{ 474 + $i * 9 }}" stroke="#5b6566" stroke-width="2"/>
<line x1="246" y1="{{ 474 + $i * 9 }}" x2="254" y2="{{ 474 + $i * 9 }}" stroke="#5b6566" stroke-width="2"/>
@endfor
<text x="184" y="524" font-size="8" fill="{{ $lab }}" text-anchor="end">filtry</text>
{{-- agregat: pompa + silnik --}}
<rect x="206" y="472" width="30" height="36" rx="3" fill="{{ $pumpColor }}" stroke="#2f3e45" stroke-width="1.5"/>
<rect x="211" y="508" width="20" height="18" rx="2" fill="#46555d"/>
<text x="262" y="494" font-size="8" fill="{{ $lab }}">agregat</text>
{{-- Czas pomiaru --}}
<text x="452" y="566" font-size="11" fill="#1d4ed8" text-anchor="end">Czas pomiaru: {{ $time ?? '-' }}</text>
</svg>
{{-- Legenda: kolor + opis + wartość (każda składowa innym kolorem) --}}
<div class="mt-3 grid grid-cols-1 sm:grid-cols-2 gap-x-5 gap-y-1.5 font-body-sm text-body-sm">
@foreach ($legend as [$color, $desc, $key])
<div class="flex items-center gap-2">
<span class="inline-block w-3.5 h-3.5 rounded-sm shrink-0" style="background: {{ $color }}"></span>
<span class="text-on-surface-variant flex-1">{{ $desc }}</span>
<span class="font-data-label text-on-surface whitespace-nowrap">{{ $f($power[$key]) }} kW</span>
</div>
@endforeach
</div>
@endif
@@ -0,0 +1,178 @@
@props([
'plot', // ['segments','raw','xMax','yMax','coeffs']
'xLabel' => 'Q [m³/h]',
'yLabel' => 'Y',
'interactive' => false, // przeciągalny marker [Q,H]
'id' => 'curve',
'initialQ' => null, // początkowe Q markera
'etaCoeffs' => [], // współczynniki η (do aktualizacji panelu)
'qInput' => null, // selektor pola Q (input)
'hOutput' => null, // selektor wyświetlania H
'etaOutput' => null, // selektor wyświetlania η
'opX' => null, // stały punkt pracy: Q (np. z pomiaru SYNDIS)
'opY' => null, // stały punkt pracy: wartość Y w tym Q
'opColor' => '#c62828', // kolor punktu pracy
'opYLabel' => null, // gdy ustawione - przy Q pokazuje też tę wartość (np. H)
'band' => false, // pionowy pasek podświetlenia przy opX (wykres η)
])
@php
$w = 380; $h = 230; $padL = 48; $padR = 12; $padT = 14; $padB = 30;
$plotW = $w - $padL - $padR; $plotH = $h - $padT - $padB;
$xMax = max(0.0001, (float) $plot['xMax']);
$yMax = max(0.0001, (float) $plot['yMax']);
$sx = fn ($x) => $padL + ($x / $xMax) * $plotW;
$sy = fn ($y) => $padT + $plotH - ($y / $yMax) * $plotH;
$fmt = fn ($v) => $v >= 100 ? number_format($v, 0, ',', ' ')
: ($v >= 10 ? number_format($v, 1, ',', '') : number_format($v, 2, ',', ''));
@endphp
<svg viewBox="0 0 {{ $w }} {{ $h }}" class="w-full h-auto select-none" xmlns="http://www.w3.org/2000/svg"
@if ($interactive)
id="{{ $id }}"
data-padl="{{ $padL }}" data-padt="{{ $padT }}" data-plotw="{{ $plotW }}" data-ploth="{{ $plotH }}"
data-xmax="{{ $xMax }}" data-ymax="{{ $yMax }}"
data-coeffs="{{ json_encode(array_values($plot['coeffs'] ?? [])) }}"
data-etacoeffs="{{ json_encode(array_values($etaCoeffs ?? [])) }}"
data-scope="{{ json_encode($plot['raw']->pluck('x')->values()->all()) }}"
@endif
>
{{-- Siatka + etykiety osi --}}
@for ($g = 0; $g <= 4; $g++)
@php $gy = $padT + $plotH * $g / 4; $vy = $yMax * (1 - $g / 4); @endphp
<line x1="{{ $padL }}" y1="{{ round($gy, 1) }}" x2="{{ $padL + $plotW }}" y2="{{ round($gy, 1) }}" stroke="var(--chart-grid)" stroke-width="1"/>
<text x="{{ $padL - 5 }}" y="{{ round($gy + 3, 1) }}" font-size="8" fill="var(--chart-tick)" text-anchor="end">{{ $fmt($vy) }}</text>
@endfor
@for ($g = 0; $g <= 4; $g++)
@php $gx = $padL + $plotW * $g / 4; $vx = $xMax * $g / 4; @endphp
<line x1="{{ round($gx, 1) }}" y1="{{ $padT }}" x2="{{ round($gx, 1) }}" y2="{{ $padT + $plotH }}" stroke="var(--chart-grid)" stroke-width="1"/>
<text x="{{ round($gx, 1) }}" y="{{ $padT + $plotH + 12 }}" font-size="8" fill="var(--chart-tick)" text-anchor="middle">{{ $fmt($vx) }}</text>
@endfor
{{-- Osie --}}
<line x1="{{ $padL }}" y1="{{ $padT }}" x2="{{ $padL }}" y2="{{ $padT + $plotH }}" stroke="var(--chart-axis)" stroke-width="1"/>
<line x1="{{ $padL }}" y1="{{ $padT + $plotH }}" x2="{{ $padL + $plotW }}" y2="{{ $padT + $plotH }}" stroke="var(--chart-axis)" stroke-width="1"/>
{{-- Krzywa wielomianowa - kolorowe segmenty wg stref Q --}}
@foreach ($plot['segments'] as $seg)
@php $poly = collect($seg['points'])->map(fn ($p) => round($sx($p['x']), 1).','.round($sy($p['y']), 1))->implode(' '); @endphp
<polyline points="{{ $poly }}" fill="none" stroke="{{ $seg['color'] }}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
@endforeach
{{-- Pasek podświetlenia przy zadanym Q (wykres η) --}}
@if ($band && $opX !== null && $opX >= 0 && $opX <= $xMax)
<rect x="{{ round($sx($opX) - 6, 1) }}" y="{{ $padT }}" width="12" height="{{ $plotH }}" fill="#f4c20d" opacity="0.22"/>
@endif
{{-- Punkty surowe (niebieskie) --}}
@foreach ($plot['raw'] as $p)
<circle cx="{{ round($sx($p->x), 1) }}" cy="{{ round($sy($p->y), 1) }}" r="3" fill="var(--chart-raw)"/>
@endforeach
{{-- Stały punkt pracy (wartości zmierzone) - na rzeczywistej wysokości, nie rzutowany na krzywą --}}
@if ($opX !== null && $opY !== null && $opX >= 0 && $opX <= $xMax)
<circle cx="{{ round($sx($opX), 1) }}" cy="{{ round($sy(max(0, $opY)), 1) }}" r="5" fill="{{ $opColor }}" stroke="#ffffff" stroke-width="1.5"/>
<text x="{{ $padL + $plotW }}" y="{{ $padT + 2 }}" font-size="7" fill="var(--color-secondary)" text-anchor="end">Q = {{ number_format(round((float) $opX, 3), 3, ',', '') }}</text>
@if ($opYLabel !== null)
<text x="{{ $padL + $plotW }}" y="{{ $padT + 11 }}" font-size="7" fill="var(--color-secondary)" text-anchor="end">{{ $opYLabel }} = {{ number_format(round((float) $opY, 1), 1, ',', ' ') }}</text>
@endif
@endif
{{-- Interaktywny marker + tooltip --}}
@if ($interactive)
<g id="{{ $id }}-tip" style="display:none">
<rect id="{{ $id }}-tiprect" rx="3" height="18" fill="#181c1d" opacity="0.9"></rect>
<text id="{{ $id }}-tiptext" font-size="9" fill="#ffffff" text-anchor="middle"></text>
</g>
<circle id="{{ $id }}-marker" r="6" fill="#a61317" stroke="#ffffff" stroke-width="2" style="cursor:grab"></circle>
@endif
{{-- Etykiety osi --}}
<text x="{{ $padL - 4 }}" y="{{ $padT - 4 }}" font-size="9" fill="var(--chart-label)">{{ $yLabel }}</text>
<text x="{{ $padL + $plotW }}" y="{{ $h - 3 }}" font-size="9" fill="var(--chart-label)" text-anchor="end">{{ $xLabel }}</text>
</svg>
@if ($interactive)
<p class="font-body-sm text-body-sm text-on-surface-variant mt-1 text-center">
Przeciągnij <span class="inline-block w-2 h-2 rounded-full align-middle" style="background:#a61317"></span> po wykresie, aby odczytać H dla danego Q.
</p>
<script>
(function () {
function init() {
const svg = document.getElementById(@json($id));
if (!svg) return;
const d = svg.dataset;
const padL = +d.padl, padT = +d.padt, plotW = +d.plotw, plotH = +d.ploth, xMax = +d.xmax, yMax = +d.ymax;
const hC = JSON.parse(d.coeffs || '[]');
const etaC = JSON.parse(d.etacoeffs || '[]');
const poly = (c, x) => c.reduce((s, ci, i) => s + ci * Math.pow(x, i), 0);
const sx = q => padL + (q / xMax) * plotW;
const sy = v => padT + plotH - (Math.max(0, v) / yMax) * plotH;
const marker = document.getElementById(@json($id . '-marker'));
const tip = document.getElementById(@json($id . '-tip'));
const tipR = document.getElementById(@json($id . '-tiprect'));
const tipT = document.getElementById(@json($id . '-tiptext'));
const qInput = @json($qInput) ? document.querySelector(@json($qInput)) : null;
const hOut = @json($hOutput) ? document.querySelector(@json($hOutput)) : null;
const etaOut = @json($etaOutput) ? document.querySelector(@json($etaOutput)) : null;
const fmt = (v, dd) => v.toFixed(dd).replace('.', ',');
// Kolor punktu wg przedziału (strefy Q) - jak segmenty krzywej.
const scope = JSON.parse(d.scope || '[]');
const BAND = { red: '#c62828', amber: '#f9a825', green: '#2e7d32' };
function bandColor(q) {
if (scope.length < 7) return BAND.green;
if (q < scope[2]) return BAND.red;
if (q < scope[3]) return BAND.amber;
if (q < scope[5]) return BAND.green;
if (q < scope[6]) return BAND.amber;
return BAND.red;
}
let hovering = false, dragging = false;
function tipVisible() { tip.style.display = (hovering || dragging) ? '' : 'none'; }
function update(q) {
q = Math.max(0, Math.min(q, xMax));
const hVal = Math.max(0, poly(hC, q));
const mx = sx(q), my = sy(hVal);
marker.setAttribute('cx', mx);
marker.setAttribute('cy', my);
marker.setAttribute('fill', bandColor(q));
const label = 'Q=' + fmt(q, 3) + ' H=' + fmt(hVal, 2);
tipT.textContent = label;
const tw = label.length * 5.2 + 10;
let tx = Math.max(padL, Math.min(mx - tw / 2, padL + plotW - tw));
const ty = Math.max(padT, my - 26);
tipR.setAttribute('x', tx); tipR.setAttribute('y', ty); tipR.setAttribute('width', tw);
tipT.setAttribute('x', tx + tw / 2); tipT.setAttribute('y', ty + 13);
if (qInput) qInput.value = fmt(q, 3);
if (hOut) hOut.textContent = fmt(hVal, 2);
if (etaOut && etaC.length) etaOut.textContent = fmt(Math.max(0, poly(etaC, q)), 2);
}
function clientToQ(e) {
const pt = svg.createSVGPoint();
pt.x = e.clientX; pt.y = e.clientY;
const loc = pt.matrixTransform(svg.getScreenCTM().inverse());
return (loc.x - padL) / plotW * xMax;
}
svg.style.cursor = 'ew-resize';
// Tooltip tylko przy najechaniu na punkt (lub w trakcie przeciągania).
marker.addEventListener('pointerenter', () => { hovering = true; tipVisible(); });
marker.addEventListener('pointerleave', () => { hovering = false; tipVisible(); });
svg.addEventListener('pointerdown', e => { dragging = true; tipVisible(); try { svg.setPointerCapture(e.pointerId); } catch (_) {} update(clientToQ(e)); });
svg.addEventListener('pointermove', e => { if (dragging) update(clientToQ(e)); });
svg.addEventListener('pointerup', () => { dragging = false; tipVisible(); });
svg.addEventListener('pointercancel', () => { dragging = false; tipVisible(); });
// Domyślnie marker w Q_n; tooltip ukryty do najechania.
const initQ = @json($initialQ);
update(initQ === null ? xMax : initQ);
tipVisible();
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script>
@endif
@@ -0,0 +1,59 @@
@props([
'chart', // ['curves'=>[['name','plot'=>[...]],...], 'xMax', 'yMax']
'q' => null, // zadane Q - pionowa linia robocza
'xLabel' => 'Q [m³/h]',
'yLabel' => 'H [m]',
])
@php
$w = 760; $h = 360; $padL = 52; $padR = 16; $padT = 16; $padB = 34;
$plotW = $w - $padL - $padR; $plotH = $h - $padT - $padB;
$xMax = max(0.0001, (float) $chart['xMax']);
$yMax = max(0.0001, (float) $chart['yMax']);
$sx = fn ($x) => $padL + ($x / $xMax) * $plotW;
$sy = fn ($y) => $padT + $plotH - ($y / $yMax) * $plotH;
$fmt = fn ($v) => $v >= 100 ? number_format($v, 0, ',', ' ')
: ($v >= 10 ? number_format($v, 1, ',', '') : number_format($v, 2, ',', ''));
@endphp
<svg viewBox="0 0 {{ $w }} {{ $h }}" class="w-full h-auto select-none" xmlns="http://www.w3.org/2000/svg">
{{-- Siatka + etykiety osi --}}
@for ($g = 0; $g <= 4; $g++)
@php $gy = $padT + $plotH * $g / 4; $vy = $yMax * (1 - $g / 4); @endphp
<line x1="{{ $padL }}" y1="{{ round($gy, 1) }}" x2="{{ $padL + $plotW }}" y2="{{ round($gy, 1) }}" stroke="var(--chart-grid)" stroke-width="1"/>
<text x="{{ $padL - 6 }}" y="{{ round($gy + 3, 1) }}" font-size="9" fill="var(--chart-tick)" text-anchor="end">{{ $fmt($vy) }}</text>
@endfor
@for ($g = 0; $g <= 5; $g++)
@php $gx = $padL + $plotW * $g / 5; $vx = $xMax * $g / 5; @endphp
<line x1="{{ round($gx, 1) }}" y1="{{ $padT }}" x2="{{ round($gx, 1) }}" y2="{{ $padT + $plotH }}" stroke="var(--chart-grid)" stroke-width="1"/>
<text x="{{ round($gx, 1) }}" y="{{ $padT + $plotH + 14 }}" font-size="9" fill="var(--chart-tick)" text-anchor="middle">{{ $fmt($vx) }}</text>
@endfor
{{-- Osie --}}
<line x1="{{ $padL }}" y1="{{ $padT }}" x2="{{ $padL }}" y2="{{ $padT + $plotH }}" stroke="var(--chart-axis)" stroke-width="1"/>
<line x1="{{ $padL }}" y1="{{ $padT + $plotH }}" x2="{{ $padL + $plotW }}" y2="{{ $padT + $plotH }}" stroke="var(--chart-axis)" stroke-width="1"/>
{{-- Linia robocza przy zadanym Q --}}
@if ($q !== null && $q > 0 && $q <= $xMax)
<line x1="{{ round($sx($q), 1) }}" y1="{{ $padT }}" x2="{{ round($sx($q), 1) }}" y2="{{ $padT + $plotH }}"
stroke="var(--color-secondary)" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="{{ round($sx($q), 1) }}" y="{{ $padT - 4 }}" font-size="9" fill="var(--color-secondary)" text-anchor="middle">Q = {{ $fmt($q) }}</text>
@endif
{{-- Nałożone krzywe H wszystkich dopasowanych pomp (kolorowe segmenty stref Q) --}}
@foreach ($chart['curves'] as $curve)
@foreach ($curve['plot']['segments'] as $seg)
@php $poly = collect($seg['points'])->map(fn ($p) => round($sx($p['x']), 1).','.round($sy($p['y']), 1))->implode(' '); @endphp
<polyline points="{{ $poly }}" fill="none" stroke="{{ $seg['color'] }}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" opacity="0.85"/>
@endforeach
{{-- Nazwa pompy przy końcu krzywej --}}
@php $last = collect($curve['plot']['raw'])->last(); @endphp
@if ($last)
<text x="{{ round($sx($last->x) + 3, 1) }}" y="{{ round($sy($last->y), 1) }}" font-size="8" fill="var(--chart-label)">{{ $curve['name'] }}</text>
@endif
@endforeach
{{-- Etykiety osi --}}
<text x="{{ $padL - 6 }}" y="{{ $padT - 5 }}" font-size="10" fill="var(--chart-label)">{{ $yLabel }}</text>
<text x="{{ $padL + $plotW }}" y="{{ $h - 4 }}" font-size="10" fill="var(--chart-label)" text-anchor="end">{{ $xLabel }}</text>
</svg>
@@ -0,0 +1,32 @@
@props(['column', 'align' => 'left'])
{{--
Klikalny nagłówek tabeli z sortowaniem.
- 'column' : klucz sortowania (musi być na whiteliście w kontrolerze).
- 'align' : left | right | center (domyślnie left).
Zachowuje bieżące filtry (cały query string), resetuje paginację (?page).
Strzałka: aktywna kolumna = kierunek (rosnąco/malejąco), pozostałe = neutralna.
--}}
@php
$activeSort = request('sort');
$activeDir = request('dir', 'asc') === 'desc' ? 'desc' : 'asc';
$isActive = $activeSort === $column;
$nextDir = $isActive && $activeDir === 'asc' ? 'desc' : 'asc';
$query = array_merge(request()->except('page'), ['sort' => $column, 'dir' => $nextDir]);
$url = request()->url() . '?' . http_build_query($query);
$alignClass = ['left' => 'text-left', 'right' => 'text-right', 'center' => 'text-center'][$align] ?? 'text-left';
$justifyClass = ['left' => 'justify-start', 'right' => 'justify-end', 'center' => 'justify-center'][$align] ?? 'justify-start';
$arrow = $isActive ? ($activeDir === 'asc' ? '▲' : '▼') : '↕';
@endphp
<th {{ $attributes->merge(['class' => "px-4 py-3 $alignClass"]) }}>
<a href="{{ $url }}"
class="group inline-flex items-center gap-1 {{ $justifyClass }} hover:text-primary transition-colors select-none">
<span>{{ $slot }}</span>
<span class="text-[0.85em] leading-none {{ $isActive ? 'text-primary' : 'text-on-surface-variant/40 group-hover:text-on-surface-variant' }}">{{ $arrow }}</span>
</a>
</th>
@@ -0,0 +1,19 @@
@props([
'name',
'label',
'value' => null,
'type' => 'text',
'required' => false,
'readonly' => false,
])
<div {{ $attributes->only('class')->merge(['class' => 'space-y-1']) }}>
<label for="{{ $name }}" class="font-data-label text-data-label text-on-surface-variant tracking-wide">
{{ $label }}@if ($required) <span class="text-primary">*</span>@endif
</label>
<input id="{{ $name }}" name="{{ $name }}" type="{{ $type }}"
value="{{ old($name, $value) }}"
@required($required) @readonly($readonly)
class="w-full bg-surface-container-low border border-outline-variant px-3 py-2 rounded focus:ring-1 focus:ring-primary focus:border-primary font-data-label text-on-surface transition-all
{{ $readonly ? 'opacity-70 cursor-not-allowed' : '' }}">
</div>
@@ -0,0 +1,92 @@
@props([
'throttle' => null, // strefa oceny dławienia: green|yellow|red|null
'loss' => null, // strefa oceny strat liniowych
'on' => false, // czy pompa załączona
'grey' => false, // tryb bez oceny (SPM - inne)
'type' => 'SPM', // etykieta typu (BASE/OPT/FULL)
])
@php
// Dynamiczny schemat układu pompowego (zastępuje statyczne PNG SPM_*).
// Kolory podstawiane wg ocen: zawór dławiący = dławienie, rurociąg = straty.
$zc = ['green' => '#2e7d32', 'yellow' => '#f9a825', 'red' => '#c62828'];
$valveColor = $grey ? '#9aa6a8' : ($zc[$throttle] ?? '#9aa6a8');
$pipeColor = $grey ? '#9aa6a8' : ($zc[$loss] ?? '#9aa6a8');
$pumpColor = $on ? '#16a34a' : '#9aa6a8';
$label = '#2f3e45'; // stały ciemny - schemat na jasnym podkładzie w obu motywach
@endphp
<svg viewBox="0 0 360 480" class="w-full h-auto max-h-[28rem] mx-auto" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="ws-ground" width="8" height="8" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="8" stroke="#b9a58c" stroke-width="2"/>
</pattern>
</defs>
{{-- ===== Instalacja powierzchniowa (tłoczenie) ===== --}}
{{-- pion z głowicy do poziomu --}}
<rect x="156" y="60" width="8" height="62" fill="{{ $pipeColor }}"/>
{{-- kolano + rurociąg tłoczny poziomy --}}
<path d="M156 64 H300" stroke="{{ $pipeColor }}" stroke-width="8" fill="none"/>
{{-- zawór dławiący (kolor = ocena dławienia) --}}
<g>
<polygon points="214,52 214,76 238,52 238,76" fill="{{ $valveColor }}" stroke="#3b3b3b" stroke-width="1.2"/>
<line x1="226" y1="48" x2="226" y2="40" stroke="#3b3b3b" stroke-width="2"/>
<rect x="219" y="36" width="14" height="5" rx="1" fill="#3b3b3b"/>
</g>
{{-- manometry p1 / p2 --}}
<circle cx="196" cy="46" r="9" fill="#ffffff" stroke="#3b3b3b" stroke-width="1.5"/>
<line x1="196" y1="55" x2="196" y2="61" stroke="#3b3b3b" stroke-width="2"/>
<text x="196" y="32" font-size="11" font-weight="600" fill="{{ $label }}" text-anchor="middle">p₁</text>
<circle cx="258" cy="46" r="9" fill="#ffffff" stroke="#3b3b3b" stroke-width="1.5"/>
<line x1="258" y1="55" x2="258" y2="61" stroke="#3b3b3b" stroke-width="2"/>
<text x="258" y="32" font-size="11" font-weight="600" fill="{{ $label }}" text-anchor="middle">p₂</text>
{{-- przepływomierz Q --}}
<rect x="280" y="55" width="20" height="18" rx="2" fill="#ffffff" stroke="#3b3b3b" stroke-width="1.5"/>
<text x="290" y="68" font-size="10" font-weight="700" fill="#3b3b3b" text-anchor="middle">Q</text>
{{-- wylot --}}
<path d="M300 64 H324 M318 58 L324 64 L318 70" stroke="#3b3b3b" stroke-width="2.5" fill="none"/>
{{-- ===== Grunt ===== --}}
<rect x="0" y="122" width="360" height="14" fill="url(#ws-ground)"/>
<line x1="0" y1="122" x2="360" y2="122" stroke="#8f706c" stroke-width="1.5"/>
{{-- ===== Otwór studni (obudowa) ===== --}}
<line x1="138" y1="122" x2="138" y2="452" stroke="#5b6566" stroke-width="3"/>
<line x1="182" y1="122" x2="182" y2="452" stroke="#5b6566" stroke-width="3"/>
<line x1="138" y1="452" x2="182" y2="452" stroke="#5b6566" stroke-width="3"/>
{{-- woda (zwierciadło dynamiczne) --}}
<rect x="140" y="232" width="40" height="219" fill="#cfeaf5" opacity="0.65"/>
{{-- zwierciadło statyczne Hst (przerywane) --}}
<line x1="140" y1="206" x2="180" y2="206" stroke="#1565c0" stroke-width="1.4" stroke-dasharray="4 3"/>
<text x="188" y="209" font-size="10" fill="{{ $label }}">H<tspan baseline-shift="sub" font-size="7">st</tspan></text>
{{-- zwierciadło dynamiczne Hd --}}
<line x1="140" y1="232" x2="180" y2="232" stroke="#1565c0" stroke-width="2"/>
<text x="188" y="235" font-size="10" fill="{{ $label }}">H<tspan baseline-shift="sub" font-size="7">d</tspan></text>
{{-- rurociąg pionowy (kolor = straty liniowe) --}}
<rect x="156" y="122" width="8" height="262" fill="{{ $pipeColor }}"/>
{{-- ===== Agregat pompowy ===== --}}
{{-- pompa --}}
<rect x="150" y="384" width="20" height="38" rx="3" fill="{{ $pumpColor }}" stroke="#2f3e45" stroke-width="1.5"/>
{{-- silnik --}}
<rect x="152" y="422" width="16" height="22" rx="2" fill="#46555d"/>
{{-- króciec ssawny --}}
<line x1="160" y1="444" x2="160" y2="450" stroke="#46555d" stroke-width="3"/>
{{-- ciśnienie tłoczne pompy Pp --}}
<circle cx="160" cy="372" r="7" fill="#ffffff" stroke="#3b3b3b" stroke-width="1.3"/>
<text x="172" y="362" font-size="10" fill="{{ $label }}">P<tspan baseline-shift="sub" font-size="7">p</tspan></text>
{{-- etykieta typu + opis (prawa strona) --}}
<text x="214" y="150" font-size="13" font-weight="700" fill="{{ $label }}">{{ $type }}</text>
<text x="214" y="170" font-size="9" fill="{{ $label }}">Układ mierzy:</text>
<text x="214" y="182" font-size="9" fill="{{ $label }}">p₁, p₂, P<tspan baseline-shift="sub" font-size="6.5">p</tspan>, Q,</text>
<text x="214" y="194" font-size="9" fill="{{ $label }}">położenie zwierciadła:</text>
<text x="214" y="206" font-size="9" fill="{{ $label }}">H<tspan baseline-shift="sub" font-size="6.5">d</tspan>, H<tspan baseline-shift="sub" font-size="6.5">st</tspan></text>
</svg>
@@ -0,0 +1,25 @@
@props([
'name',
'label',
'options', // kolekcja: [klucz => etykieta] lub lista etykiet (gdy valueIsLabel)
'selected' => null,
'required' => false,
'placeholder' => null,
'valueIsLabel' => false, // true → value = etykieta (słowniki opisowe: WLL_SYS_TYPE itp.)
])
<div {{ $attributes->only('class')->merge(['class' => 'space-y-1']) }}>
<label for="{{ $name }}" class="font-data-label text-data-label text-on-surface-variant tracking-wide">
{{ $label }}@if ($required) <span class="text-primary">*</span>@endif
</label>
<select id="{{ $name }}" name="{{ $name }}" @required($required)
class="w-full bg-surface-container-low border border-outline-variant px-3 py-2 rounded focus:ring-1 focus:ring-primary focus:border-primary font-data-label text-on-surface transition-all">
@if ($placeholder)
<option value="">{{ $placeholder }}</option>
@endif
@foreach ($options as $key => $text)
@php($optionValue = $valueIsLabel ? $text : $key)
<option value="{{ $optionValue }}" @selected((string) old($name, $selected) === (string) $optionValue)>{{ $text }}</option>
@endforeach
</select>
</div>
+44
View File
@@ -0,0 +1,44 @@
<x-app-layout :active="'dashboard'" title="SoftSPM 2 - Mapa Obiektów">
<div class="space-y-stack-gap">
<div>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Mapa Obiektów</h1>
<p class="font-body-sm text-body-sm text-on-surface-variant mt-1">
Zalogowano jako <span class="font-data-label">{{ auth()->user()?->USR_USERNAME }}</span>
· Oddział: <span class="font-data-label">{{ session('department') ?? '-' }}</span>
</p>
</div>
{{-- Mapa: studnie naniesione wg lokalnych współrzędnych geodezyjnych (siatka kopalniana). --}}
<div class="relative rounded-xl overflow-hidden min-h-[74vh] shadow-card border border-outline-variant/30 bg-surface-container">
{{-- Podkład - zdjęcie wg motywu (przyciemnione, dla efektu) --}}
<img src="{{ asset('images/dashboard_01.png') }}" alt="" class="absolute inset-0 w-full h-full object-cover opacity-50 dark:hidden">
<img src="{{ asset('images/dashboard_02.png') }}" alt="" class="absolute inset-0 w-full h-full object-cover opacity-60 hidden dark:block">
<div class="absolute inset-0 bg-on-surface/20 dark:bg-black/45"></div>
{{-- Subtelna siatka (czyta się jak plan/mapa) --}}
<div class="absolute inset-0 opacity-[0.18]"
style="background-image: linear-gradient(rgba(255,255,255,.5) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.5) 1px, transparent 1px); background-size: 48px 48px;"></div>
{{-- Kropki studni --}}
@forelse ($points as $p)
<a href="{{ route('well-operation.show', $p->drs_id) }}"
aria-label="Studnia {{ $p->name }} - {{ $p->on ? 'pompa pracuje' : 'pompa nie pracuje' }}"
style="left: {{ $p->left }}%; top: {{ $p->top }}%"
class="group absolute z-20 -translate-x-1/2 -translate-y-1/2">
<span class="block w-3 h-3 rounded-full ring-2 ring-white/80 shadow-md transition-transform group-hover:scale-150 {{ $p->on ? 'bg-green-500' : 'bg-red-500' }}"></span>
<span class="pointer-events-none absolute left-1/2 bottom-full mb-1.5 -translate-x-1/2 px-2 py-0.5 rounded bg-on-surface text-inverse-on-surface font-data-label text-[11px] whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity z-40">{{ $p->name }}</span>
</a>
@empty
<div class="absolute inset-0 grid place-items-center z-20">
<p class="font-body-md text-body-md text-white/90 bg-on-surface/50 px-4 py-2 rounded-lg">Brak studni z poprawnymi współrzędnymi dla tego oddziału.</p>
</div>
@endforelse
</div>
{{-- Legenda pod mapą --}}
<div class="flex flex-wrap items-center gap-x-6 gap-y-2 px-card-inner-padding py-3 bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card font-body-sm text-body-sm">
<span class="font-title-sm text-title-sm text-on-surface">Studnie w eksploatacji: <span class="font-data-label">{{ $points->count() }}</span></span>
<span class="flex items-center gap-2"><span class="w-3 h-3 rounded-full bg-green-500 ring-2 ring-white/70"></span> Pompa pracuje <span class="font-data-label text-on-surface-variant">({{ $onCount }})</span></span>
<span class="flex items-center gap-2"><span class="w-3 h-3 rounded-full bg-red-500 ring-2 ring-white/70"></span> Pompa nie pracuje <span class="font-data-label text-on-surface-variant">({{ $offCount }})</span></span>
</div>
</div>
</x-app-layout>
@@ -0,0 +1,91 @@
<x-app-layout :active="'well-operation.index'" title="SoftSPM 2 - Eksploatacja studni">
<div class="space-y-stack-gap">
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('status') }}</div>
@endif
<div>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Eksploatacja studni</h1>
<p class="font-body-sm text-body-sm text-on-surface-variant mt-1">
Oddział: <span class="font-data-label">{{ $department ?? '-' }}</span>
· Wyników: <span class="font-data-label">{{ $wells->total() }}</span>
</p>
</div>
{{-- Wyszukiwarka --}}
<form method="GET" action="{{ route('well-operation.index') }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<div class="flex flex-wrap items-end gap-4">
<div class="space-y-1">
<label for="q" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Studnia</label>
<input id="q" name="q" type="text" value="{{ $search }}" placeholder="nazwa studni"
class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded focus:ring-1 focus:ring-primary focus:border-primary font-data-label text-on-surface">
</div>
<div class="space-y-1">
<label for="status" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Status</label>
<select id="status" name="status"
class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded focus:ring-1 focus:ring-primary focus:border-primary font-data-label text-on-surface">
@foreach ($statusOptions as $id => $label)
<option value="{{ $id }}" {{ (int) $status === (int) $id ? 'selected' : '' }}>{{ $label }}</option>
@endforeach
<option value="{{ $statusAll }}" {{ (int) $status === $statusAll ? 'selected' : '' }}>Wszystkie</option>
</select>
</div>
<button type="submit"
class="bg-secondary text-on-secondary px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:opacity-90 transition-all active:scale-95">
Szukaj
</button>
</div>
</form>
{{-- Lista studni w eksploatacji --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<table class="w-full text-left">
<thead class="bg-secondary/10 border-b border-outline-variant/40">
<tr class="font-data-label text-data-label text-on-surface-variant tracking-wide">
<th class="px-4 py-3 w-12">Lp.</th>
<x-sortable-th column="name">Studnia</x-sortable-th>
<x-sortable-th column="bariera">Ujęcie / Bariera</x-sortable-th>
<x-sortable-th column="pump">Pompa</x-sortable-th>
<x-sortable-th column="pump_no">Nr fabryczny pompy [S/N]</x-sortable-th>
<x-sortable-th column="engine">Silnik</x-sortable-th>
<x-sortable-th column="engine_no">Nr fabryczny silnika [S/N]</x-sortable-th>
<x-sortable-th column="type">Typ</x-sortable-th>
<th class="px-4 py-3 text-center">Stan</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30 font-body-md text-body-md">
@forelse ($wells as $well)
<tr class="hover:bg-surface-container transition-colors cursor-pointer"
onclick="location.href='{{ route('well-operation.show', $well->drs_id) }}'">
<td class="px-4 py-3 font-data-label text-on-surface-variant">{{ $wells->firstItem() + $loop->index }}.</td>
<td class="px-4 py-3 font-semibold text-on-surface">{{ $well->well_name }}</td>
<td class="px-4 py-3">{{ $well->bariera }}</td>
<td class="px-4 py-3">{{ $well->pump_name }}</td>
<td class="px-4 py-3 font-data-label">{{ $well->pump_no }}</td>
<td class="px-4 py-3">{{ $well->engine_name }}</td>
<td class="px-4 py-3 font-data-label">{{ $well->engine_no }}</td>
<td class="px-4 py-3">{{ $well->sys_type }}</td>
<td class="px-4 py-3 text-center">
@if ($well->onoff === 'ON')
<span class="status-chip status-chip-ok">ON</span>
@else
<span class="status-chip status-chip-alert">OFF</span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="9" class="px-4 py-8 text-center font-body-md text-body-md text-on-surface-variant">
Brak studni dla wybranych kryteriów.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div>{{ $wells->links() }}</div>
</div>
</x-app-layout>
@@ -0,0 +1,247 @@
<x-app-layout :active="'well-operation.index'" title="SoftSPM 2 - Parametry: {{ $well->well_name }}">
@php
$fmt = fn ($v, $d = 2) => $v === null || $v === '' ? '-' : number_format(round((float) $v, $d), $d, ',', ' ');
$iv = fn ($v, $d) => ($v === null || $v === '') ? '' : number_format(round((float) $v, $d), $d, ',', '');
// <option> nie renderuje HTML - zamiana sup/sub na unicode, reszta tagów usuwana.
$optClean = fn ($s) => strip_tags(strtr((string) $s, [
'<sup>2</sup>' => '²', '<sup>3</sup>' => '³',
'<sub>0</sub>' => '₀', '<sub>1</sub>' => '₁', '<sub>2</sub>' => '₂',
]));
$isOn = $onoff && $onoff->DRD_VALUE === 'ON';
$qMin = $measurement && $measurement->q !== null ? (float) $measurement->q / 60 : null; // m³/min
// Pola pomiaru: [name, etykieta(html), wartość, miejsca dziesiętne].
$pf = [
['q', 'Wydajność Q [m³/min]', $qMin, 3],
['hd', 'Zwierciadło dynamiczne H<sub>D</sub> [m]', $measurement?->hd, 2],
['hst', 'Zwierciadło statyczne H<sub>ST</sub> [m]', $measurement?->hst, 2],
['hst0', 'Zwierciadło statyczne H<sub>ST0</sub> [m]', $measurement?->hst0, 1],
['p1', 'Ciśnienie p<sub>1</sub> [m]', $measurement?->p1, 2],
['p2', 'Ciśnienie p<sub>2</sub> [m]', $measurement?->p2, 2],
['pb', 'Ciśnienie barometryczne p<sub>b</sub> [hPa]', $extra->pb, 2],
['vm3', 'Ilość wypompowanej wody V [m³]', $measurement?->vm3, 2],
['wtemp', 'Temp. wody T<sub>W</sub> [°C]', $measurement?->wtemp, 1],
['pp', 'Ciśnienie p<sub>p</sub> [m]', $measurement?->pp, 2],
['hh', 'Wysokość H<sub>H</sub> [m]', $measurement?->hh, 2],
['throttle', 'Otwarcie przepustnicy [%]', $extra->throttle, 1],
'---',
['pe', 'Moc czynna P<sub>e</sub> [kW]', $measurement?->pe, 2],
['energy_kwh', 'Pobór energii E [kWh]', $extra->energy_kwh, 2],
['i', 'Pobór prądu I [A]', $measurement?->i, 2],
['u', 'Napięcie zasilania U [V]', $measurement?->u, 0],
['cos', 'cos(φ)', $measurement?->cos, 3],
['work_time', 'Czas pracy agregatu t [h]', $measurement?->work_time, 2],
['unit_energy', 'Jednostkowe zużycie energii [Wh/m³/m]', $extra->unit_energy, 2],
];
$inputClass = 'param-input w-32 ml-auto block text-right font-data-label text-on-surface rounded border px-2 py-0.5 bg-surface-container-low border-outline-variant read-only:border-transparent read-only:bg-transparent read-only:px-0 read-only:cursor-default focus:outline-none focus:ring-1 focus:ring-primary';
// Przyciski nawigacyjne (podstrony 401). Tylko „Ocena pracy układu" zbudowana.
$nav = [
['Ocena pracy układu', route('well-operation.show', $well->drs_id), true],
['Historia ocen', null, false],
['Historia agregatów', null, false],
['Historia postojów', null, false],
['Karta otworu', null, false],
['Parametry chemiczne', null, false],
['Stan studni', null, false],
['Kalibracja', null, false],
];
@endphp
<div class="space-y-stack-gap">
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('status') }}</div>
@endif
@if (session('error'))
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('error') }}</div>
@endif
@if ($errors->any())
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ $errors->first() }}</div>
@endif
{{-- Powrót do oceny pracy układu --}}
<div class="flex items-center gap-2">
<a href="{{ route('well-operation.show', $well->drs_id) }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Ocena pracy układu</a>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-stack-gap items-start">
{{-- ===== LEWA: studnia + parametry techniczne + pracy ===== --}}
<div class="space-y-stack-gap">
{{-- Studnia --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="bg-secondary text-on-secondary px-card-inner-padding py-2 font-title-sm text-title-sm">Studnia</div>
<div class="py-4 text-center font-headline-lg text-headline-lg {{ $isOn ? 'text-green-600' : 'text-on-surface' }}">{{ $well->well_name }}</div>
</div>
{{-- Data ostatniej aktualizacji danych z SYNDIS --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="bg-secondary text-on-secondary px-card-inner-padding py-2 font-title-sm text-title-sm">Data ostatniej aktualizacji danych z SYNDIS</div>
<div class="py-3 text-center font-headline-md text-headline-md text-secondary">{{ $measurement->date ?? '-' }}</div>
</div>
{{-- Parametry techniczne studni --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="bg-secondary text-on-secondary px-card-inner-padding py-2 font-title-sm text-title-sm">Parametry techniczne studni</div>
<table class="w-full font-body-md text-body-md">
<tbody class="divide-y divide-outline-variant/30">
@php
$tech = collect([
['Nazwa', $well->well_name],
['Kategoria SPM', $category ?: '-'],
['Data zabudowy pompy', $availableDate ?: '-'],
]);
foreach ($techParams as $tp) { $tech->push([$tp->label, $tp->value ?: '-']); }
@endphp
@foreach ($tech as [$label, $value])
<tr>
<td class="px-4 py-1.5 w-1/2 bg-surface-container/50 text-on-surface-variant font-data-label text-data-label">{!! $label !!}</td>
<td class="px-4 py-1.5">{{ $value }}</td>
</tr>
@endforeach
<tr><td class="px-4 py-1.5 bg-surface-container/50 text-on-surface-variant font-data-label text-data-label">Głębokość zabudowy pompy L<sub>P</sub> [m]</td><td class="px-4 py-1.5 font-data-label">{{ $fmt($lp, 2) }}</td></tr>
<tr><td class="px-4 py-1.5 bg-surface-container/50 text-on-surface-variant font-data-label text-data-label">Średnica rurociągu D<sub>R</sub> [mm]</td><td class="px-4 py-1.5 font-data-label">{{ $dr === null ? '-' : $fmt((float) $dr * 1000, 0) }}</td></tr>
<tr><td class="px-4 py-1.5 bg-surface-container/50 text-on-surface-variant font-data-label text-data-label">m [m]</td><td class="px-4 py-1.5 font-data-label">{{ $fmt($mParam, 2) }}</td></tr>
</tbody>
</table>
</div>
{{-- Parametry pracy (z ręczną edycją) --}}
<form id="measForm" method="POST" action="{{ route('well-operation.params-store', $well->drs_id) }}">
@csrf
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="bg-secondary text-on-secondary px-card-inner-padding py-2 font-title-sm text-title-sm flex items-center justify-between gap-2">
<span>Parametry pracy studni i układu pompowego</span>
<span class="flex items-center gap-2">
<button type="button" id="measEdit" title="Wprowadź ręcznie pomiar"
class="px-2.5 py-1 rounded-full bg-on-secondary/15 hover:bg-on-secondary/25 font-data-label text-data-label">✎ Modyfikuj</button>
<span id="measSave" class="hidden items-center gap-2">
<button type="submit" class="px-2.5 py-1 rounded-full bg-on-secondary text-secondary font-data-label text-data-label hover:opacity-90">Zapisz</button>
<button type="button" onclick="location.reload()" class="px-2.5 py-1 rounded-full bg-on-secondary/15 font-data-label text-data-label hover:bg-on-secondary/25">Anuluj</button>
</span>
</span>
</div>
<table class="w-full font-body-md text-body-md">
<tbody class="divide-y divide-outline-variant/30">
<tr>
<td class="px-4 py-1.5 w-2/3 bg-surface-container/50 text-on-surface-variant font-data-label text-data-label">Data pomiaru</td>
<td class="px-4 py-1 text-right">
<div class="flex items-center justify-end gap-1">
<input type="date" name="date" value="{{ now()->format('Y-m-d') }}" readonly class="{{ $inputClass }} w-36">
<input type="time" name="time" value="{{ now()->format('H:i') }}" readonly class="{{ $inputClass }} w-24">
</div>
</td>
</tr>
@foreach ($pf as $row)
@if ($row === '---')
<tr><td colspan="2" class="px-4 py-1 bg-surface-container-low"></td></tr>
@else
<tr>
<td class="px-4 py-1.5 w-2/3 bg-surface-container/50 text-on-surface-variant font-data-label text-data-label">{!! $row[1] !!}</td>
<td class="px-4 py-1 text-right">
<input type="text" name="{{ $row[0] }}" value="{{ $iv($row[2], $row[3]) }}" readonly class="{{ $inputClass }}">
</td>
</tr>
@endif
@endforeach
</tbody>
</table>
</div>
</form>
</div>
{{-- ===== PRAWA: bilans mocy + rysunek układu ===== --}}
<div class="space-y-stack-gap">
{{-- Bilans mocy --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="bg-secondary text-on-secondary px-card-inner-padding py-2 font-title-sm text-title-sm">Bilans mocy</div>
@if ($power)
<table class="w-full font-body-md text-body-md">
<tbody class="divide-y divide-outline-variant/30">
<tr><td colspan="2" class="px-4 py-1.5 bg-surface-container text-center font-semibold text-on-surface">Moc przekazana</td></tr>
<tr><td class="px-4 py-1.5 w-2/3 text-on-surface-variant">Podnoszenie stałe</td><td class="px-4 py-1.5 text-right font-data-label">{{ $fmt($power['pst']) }} [kW]</td></tr>
<tr><td class="px-4 py-1.5 text-on-surface-variant">Geo - hydraulika</td><td class="px-4 py-1.5 text-right font-data-label">{{ $fmt($power['pgh']) }} [kW]</td></tr>
<tr><td class="px-4 py-1.5 text-on-surface-variant">Odpływ</td><td class="px-4 py-1.5 text-right font-data-label">{{ $fmt($power['pp2']) }} [kW]</td></tr>
<tr><td colspan="2" class="px-4 py-1.5 bg-surface-container text-center font-semibold text-on-surface">Moc stracona</td></tr>
<tr><td class="px-4 py-1.5 text-on-surface-variant">Agregat + zasilanie</td><td class="px-4 py-1.5 text-right font-data-label">{{ $fmt($power['paz']) }} [kW]</td></tr>
<tr><td class="px-4 py-1.5 text-on-surface-variant">Straty liniowe</td><td class="px-4 py-1.5 text-right font-data-label">{{ $fmt($power['psl']) }} [kW]</td></tr>
<tr><td class="px-4 py-1.5 text-on-surface-variant">Straty w armaturze</td><td class="px-4 py-1.5 text-right font-data-label">{{ $fmt($power['psa']) }} [kW]</td></tr>
<tr><td colspan="2" class="px-2 py-1"></td></tr>
<tr><td class="px-4 py-1.5 text-on-surface-variant">Moc pobrana P<sub>e</sub></td><td class="px-4 py-1.5 text-right font-data-label font-semibold">{{ $fmt($power['pe']) }} [kW]</td></tr>
<tr><td class="px-4 py-1.5 text-on-surface-variant">Moc zużyta &Sigma;</td><td class="px-4 py-1.5 text-right font-data-label font-semibold">{{ $fmt($power['sum']) }} [kW]</td></tr>
</tbody>
</table>
@else
<p class="p-card-inner-padding font-body-sm text-body-sm text-on-surface-variant">Brak danych pomiarowych do bilansu mocy.</p>
@endif
</div>
{{-- Układ pompowy (rysunek rozkładu mocy) --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="bg-secondary text-on-secondary px-card-inner-padding py-2 font-title-sm text-title-sm">Układ pompowy - {{ $well->well_name }}</div>
<div class="p-card-inner-padding">
<div class="rounded border border-outline-variant/40 p-2" style="background:#f7fafb">
<x-power-schematic :power="$power" :on="$isOn" :time="$measurement?->date" />
</div>
<p class="mt-2 font-body-sm text-body-sm text-on-surface-variant">
Pompa: <span class="font-semibold text-on-surface">{{ $well->pump_name ?: '-' }}</span>
@if ($well->pump_no) <span class="font-data-label">({{ $well->pump_no }})</span> @endif
· Silnik: <span class="font-semibold text-on-surface">{{ $well->engine_name ?: '-' }}</span>
@if ($well->engine_no) <span class="font-data-label">({{ $well->engine_no }})</span> @endif
</p>
</div>
</div>
{{-- Historia po zadanym parametrze --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="bg-secondary text-on-secondary px-card-inner-padding py-2 font-title-sm text-title-sm">Historia po zadanym parametrze</div>
<div class="p-card-inner-padding flex flex-wrap items-end gap-3">
<div class="space-y-1">
<label for="histparam" class="font-data-label text-data-label text-on-surface-variant tracking-wide">Wybierz parametr</label>
<select id="histparam" class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded font-data-label text-on-surface">
<option value="0">Wybierz z listy</option>
@foreach ($historyParams as $id => $label)
<option value="{{ $id }}">{{ $optClean($label) }}</option>
@endforeach
</select>
</div>
<x-btn :disabled="true" title="W przygotowaniu (historia parametru)">Pokaż</x-btn>
</div>
</div>
{{-- Przyciski nawigacyjne (podstrony) - maks. 2 w rzędzie --}}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
@foreach ($nav as [$label, $href, $active])
@if ($active)
<x-btn as="a" href="{{ $href }}" class="w-full">{{ $label }}</x-btn>
@else
<x-btn :disabled="true" title="W przygotowaniu" class="w-full">{{ $label }}</x-btn>
@endif
@endforeach
<x-btn :disabled="true" title="W przygotowaniu" class="w-full sm:col-span-2">Pomiary na żądanie</x-btn>
</div>
</div>
</div>
</div>
{{-- Toggle ręcznej edycji pomiaru --}}
<script>
(function () {
const eb = document.getElementById('measEdit');
const sv = document.getElementById('measSave');
const fm = document.getElementById('measForm');
if (! eb || ! fm) return;
eb.addEventListener('click', function () {
fm.querySelectorAll('.param-input').forEach(i => i.removeAttribute('readonly'));
eb.classList.add('hidden');
sv.classList.remove('hidden');
sv.classList.add('flex');
const q = fm.querySelector('[name="q"]');
if (q) q.focus();
});
})();
</script>
</x-app-layout>
@@ -0,0 +1,168 @@
<x-app-layout :active="'well-operation.index'" title="SoftSPM 2 - {{ $well->well_name }}">
@php
$fmt = fn ($v, $d = 2) => $v === null || $v === '' ? '-' : number_format(round((float) $v, $d), $d, ',', ' ');
// Klasy komórki oceny wg strefy.
$zoneCell = [
'green' => 'bg-green-100 text-green-900 dark:bg-green-900/30 dark:text-green-200',
'yellow' => 'bg-amber-100 text-amber-900 dark:bg-amber-900/30 dark:text-amber-200',
'red' => 'bg-red-100 text-red-900 dark:bg-red-900/30 dark:text-red-200',
];
$zoneHex = ['green' => '#2e7d32', 'yellow' => '#f9a825', 'red' => '#c62828'];
// Kolor punktu pracy na wykresie Hp = ocena H=f(Q) (jak Evaluation4_HfQ w legacy).
$hfqZone = $assessments['hfq']['zone'] ?? null;
$hfqColor = $hfqZone ? $zoneHex[$hfqZone] : '#5b6566';
$spmType = 'SPM - '.strtoupper(str_replace(['images/spm_', '.png'], '', $spmImage));
$rows = [
['Przedział Q', $assessments['q'] ?? null],
['H = f(Q)', $assessments['hfq'] ?? null],
['Dławienie', $assessments['throttle'] ?? null],
['Straty liniowe', $assessments['loss'] ?? null],
];
$isOn = $onoff && $onoff->DRD_VALUE === 'ON';
@endphp
<div class="space-y-stack-gap">
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('status') }}</div>
@endif
@if (session('error'))
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">{{ session('error') }}</div>
@endif
{{-- Nagłówek --}}
<div class="flex flex-wrap items-center justify-between gap-3">
<div class="flex items-center gap-2">
<a href="{{ route('well-operation.index') }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Eksploatacja studni</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">{{ $well->well_name }}</h1>
</div>
@if ($isOn)
<span class="status-chip status-chip-ok">Załączona</span>
@else
<span class="status-chip status-chip-alert">Wyłączona</span>
@endif
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-stack-gap items-start">
{{-- ===== LEWA KOLUMNA ===== --}}
<div class="space-y-stack-gap">
{{-- Oceny pracy układu pompowego --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="bg-secondary text-on-secondary px-card-inner-padding py-2 font-title-sm text-title-sm">Oceny pracy układu pompowego</div>
@if ($assessments === null)
<p class="p-card-inner-padding font-body-sm text-body-sm text-on-surface-variant">Studnia typu „SPM - inne" - brak automatycznej oceny.</p>
@else
<table class="w-full font-body-md text-body-md">
<tbody class="divide-y divide-outline-variant/30">
@foreach ($rows as [$label, $a])
<tr>
<td class="px-4 py-2 w-40 bg-surface-container/60 text-on-surface-variant font-data-label text-data-label align-top">{{ $label }}</td>
<td class="px-4 py-2 {{ $a ? $zoneCell[$a['zone']] : 'text-on-surface-variant' }}">{{ $a['text'] ?? 'Brak oceny' }}</td>
</tr>
@endforeach
</tbody>
</table>
@endif
</div>
{{-- Układ pompowy + schemat SPM --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="bg-secondary text-on-secondary px-card-inner-padding py-2 font-title-sm text-title-sm">Układ pompowy - {{ $well->well_name }}</div>
<div class="p-card-inner-padding space-y-3">
<dl class="grid grid-cols-3 gap-x-4 gap-y-1.5 font-body-md text-body-md">
<dt class="text-on-surface-variant">Ujęcie / Bariera</dt><dd class="col-span-2">{{ $well->bariera ?: '-' }}</dd>
<dt class="text-on-surface-variant">Pompa</dt><dd class="col-span-2 font-semibold">{{ $well->pump_name ?: '-' }} <span class="font-data-label text-on-surface-variant">{{ $well->pump_no ? '· '.$well->pump_no : '' }}</span></dd>
<dt class="text-on-surface-variant">Silnik</dt><dd class="col-span-2 font-semibold">{{ $well->engine_name ?: '-' }} <span class="font-data-label text-on-surface-variant">{{ $well->engine_no ? '· '.$well->engine_no : '' }}</span></dd>
<dt class="text-on-surface-variant">Q<sub>n</sub> [m³/min]</dt><dd class="col-span-2 font-data-label">{{ $fmt($qnMin, 3) }}</dd>
</dl>
<div class="relative rounded border border-outline-variant/40 p-2" style="background:#f7fafb">
<x-well-schematic
:throttle="$assessments['throttle']['zone'] ?? null"
:loss="$assessments['loss']['zone'] ?? null"
:on="$isOn"
:grey="$grey"
:type="$spmType" />
{{-- Status diagnostyki --}}
<span class="absolute top-3 right-3 inline-flex items-center gap-1 px-2 py-0.5 rounded-full font-data-label text-data-label
@if ((int) $diag === 2) bg-red-100 text-red-800 @elseif ((int) $diag === 1) bg-amber-100 text-amber-800 @else bg-green-100 text-green-800 @endif">
@if ((int) $diag === 2) Diagnostyka: błąd @elseif ((int) $diag === 1) Diagnostyka: ostrzeżenie @else Diagnostyka: OK @endif
</span>
</div>
@if ($measurement)
<p class="text-center font-data-label text-data-label text-secondary">Data odpytania SYNDIS: {{ $measurement->date }}</p>
@endif
</div>
</div>
{{-- Stan pracy --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="bg-secondary text-on-secondary px-card-inner-padding py-2 font-title-sm text-title-sm">
Stan pracy - {{ $isOn ? 'Załączona' : 'Wyłączona' }}
@if ($onoff) <span class="font-data-label text-data-label opacity-80">[{{ $onoff->DRD_AUDIT_DATE }}]</span> @endif
</div>
<div class="p-card-inner-padding flex items-center gap-4">
<div class="grid place-items-center w-28 h-14 rounded-lg font-headline-md text-headline-md {{ $isOn ? 'bg-green-600 text-white' : 'bg-surface-container text-on-surface-variant/50' }}">ON</div>
<div class="grid place-items-center w-28 h-14 rounded-lg font-headline-md text-headline-md {{ ! $isOn ? 'bg-red-600 text-white' : 'bg-surface-container text-on-surface-variant/50' }}">OFF</div>
@if ($onoff && ! empty($onoff->reason))
<div class="font-body-sm text-body-sm text-on-surface-variant">
Powód: <span class="font-semibold text-on-surface">{{ $onoff->reason }}</span>
</div>
@endif
</div>
</div>
{{-- Akcje - pod stanem pracy (siatka 2-kol, jak na stronie parametrów) --}}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 pt-1">
<x-btn as="a" href="{{ route('well-operation.params', $well->drs_id) }}" class="w-full">Parametry pracy układu</x-btn>
<form method="POST" action="{{ route('well-operation.fetch', $well->drs_id) }}">
@csrf
<x-btn type="submit" class="w-full">Pobierz aktualne dane</x-btn>
</form>
</div>
</div>
{{-- ===== PRAWA KOLUMNA ===== --}}
<div class="space-y-stack-gap">
{{-- Ocena energochłonności --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="bg-secondary text-on-secondary px-card-inner-padding py-2 font-title-sm text-title-sm">Ocena energochłonności</div>
<div class="p-card-inner-padding">
@if ($energy)
<x-energy-gauge :eta="$energy['eta']" :ry="$energy['ry']" :yg="$energy['yg']" :zone="$energy['zone']" />
@else
<div class="h-12 rounded bg-surface-container grid place-items-center font-body-sm text-body-sm text-on-surface-variant">Brak oceny</div>
@endif
</div>
</div>
{{-- Wysokość podnoszenia --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="bg-secondary text-on-secondary px-card-inner-padding py-2 font-title-sm text-title-sm">Wysokość podnoszenia</div>
<div class="p-card-inner-padding">
@if ($hPlot)
<x-pump-curve :plot="$hPlot" x-label="Q [m³/min]" y-label="H [m]" :op-x="$opQ" :op-y="$opH" :op-color="$hfqColor" op-y-label="H" />
@else
<p class="py-8 text-center font-body-sm text-body-sm text-on-surface-variant">Brak danych próby pompy.</p>
@endif
</div>
</div>
{{-- Sprawność --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<div class="bg-secondary text-on-secondary px-card-inner-padding py-2 font-title-sm text-title-sm">Sprawność</div>
<div class="p-card-inner-padding">
@if ($etaPlot)
<x-pump-curve :plot="$etaPlot" x-label="Q [m³/min]" y-label="η [-]" :op-x="$opQ" :op-y="$opEta" op-color="#5b6566" :band="true" />
@else
<p class="py-8 text-center font-body-sm text-body-sm text-on-surface-variant">Brak danych próby pompy.</p>
@endif
</div>
</div>
</div>
</div>
</div>
</x-app-layout>
+15
View File
@@ -0,0 +1,15 @@
@php($sec = collect(config('navigation'))->firstWhere('key', $sectionKey))
<x-app-layout :section="$sectionKey" :title="'SoftSPM 2 - '.($sec['label'] ?? '')">
<div class="space-y-stack-gap">
<h1 class="font-headline-lg text-headline-lg text-on-surface">{{ $sec['label'] ?? '' }}</h1>
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-8 min-h-[40vh] flex items-center justify-center">
<div class="text-center">
<p class="font-body-md text-body-md text-on-surface-variant">
Wybierz pozycję z menu po lewej stronie.
</p>
</div>
</div>
</div>
</x-app-layout>
File diff suppressed because one or more lines are too long
+48
View File
@@ -0,0 +1,48 @@
<x-app-layout :active="'wells.index'" title="SoftSPM 2 - Nowa studnia">
<div class="max-w-3xl space-y-stack-gap">
<div class="flex items-center gap-2">
<a href="{{ route('wells.index') }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Wróć</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Nowa studnia</h1>
</div>
@if ($errors->any())
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">
{{ $errors->first() }}
</div>
@endif
<form method="POST" action="{{ route('wells.store') }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding space-y-4">
@csrf
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<x-well-field name="name" label="Nazwa studni" :value="old('name')" required class="sm:col-span-2" />
<x-well-select name="water_intake_id" label="Ujęcie / bariera" :options="$waterIntakes" :selected="old('water_intake_id')" required placeholder="- wybierz -" />
<x-well-select name="category_id" label="Kategoria" :options="$categories" :selected="old('category_id', 19)" required />
<x-well-field name="diameter" label="Średnica rurociągu" :value="old('diameter')" />
<x-well-field name="depth" label="Głębokość studni" :value="old('depth')" />
<x-well-field name="supply" label="Głębokość zasypu" :value="old('supply')" />
<x-well-select name="status_id" label="Status" :options="$statuses" :selected="old('status_id')" required placeholder="- wybierz -" />
<x-well-field name="latitude" label="Szerokość geograficzna" :value="old('latitude')" />
<x-well-field name="longitude" label="Długość geograficzna" :value="old('longitude')" />
<x-well-field name="available_date" label="Data oddania" type="date" :value="old('available_date')" />
</div>
<div class="flex items-center gap-2 pt-2">
<button type="submit"
class="bg-primary text-on-primary px-6 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">
Zapisz
</button>
<a href="{{ route('wells.index') }}"
class="px-5 py-2.5 rounded font-title-sm text-title-sm text-on-surface-variant hover:bg-surface-container transition-all">
Anuluj
</a>
</div>
</form>
</div>
</x-app-layout>
+75
View File
@@ -0,0 +1,75 @@
<x-app-layout :active="'wells.index'" title="SoftSPM 2 - Edycja studni">
@php
// Data/godzina zabudowy z ostatniej wymiany agregatu (jak site301E).
$dateVal = $well->wle_date_from ? substr($well->wle_date_from, 0, 10) : ($well->available_date ?? '');
$timeVal = $well->wle_date_from ? substr($well->wle_date_from, 11, 5) : '';
@endphp
<div class="max-w-3xl space-y-stack-gap">
<div class="flex items-center gap-2">
<a href="{{ route('wells.index') }}" class="font-body-sm text-body-sm text-on-surface-variant hover:text-primary">← Wróć</a>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Edycja: {{ $well->name }}</h1>
</div>
@if ($errors->any())
<div class="status-chip-alert px-3 py-2 rounded-lg font-body-sm text-body-sm">
{{ $errors->first() }}
</div>
@endif
<form method="POST" action="{{ route('wells.update', $well->drs_id) }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding space-y-4">
@csrf
@method('PUT')
<input type="hidden" name="well_id" value="{{ $well->well_id }}">
<input type="hidden" name="wle_id" value="{{ $well->wle_id }}">
{{-- Zabudowany agregat (tylko do odczytu) --}}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<x-well-field name="_pump_name" label="Pompa" :value="$well->pump_name" readonly />
<x-well-field name="_pump_no" label="Nr pompy" :value="$well->pump_no" readonly />
<x-well-field name="_engine_name" label="Silnik" :value="$well->engine_name" readonly />
<x-well-field name="_engine_no" label="Nr silnika" :value="$well->engine_no" readonly />
</div>
<hr class="border-outline-variant/40">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<x-well-field name="name" label="Nazwa studni" :value="$well->name" required class="sm:col-span-2" />
<x-well-select name="water_intake_id" label="Ujęcie / bariera" :options="$waterIntakes" :selected="$well->water_intake_id" required />
<x-well-select name="category_id" label="Kategoria" :options="$categories" :selected="$well->category_id" required />
<x-well-select name="status_id" label="Status" :options="$statuses" :selected="$well->status_id" required />
<x-well-field name="available_date" label="Data zabudowy" type="date" :value="$dateVal" />
<x-well-field name="build_time" label="Godzina zabudowy" type="time" :value="$timeVal" />
<x-well-field name="latitude" label="Szerokość geograficzna" :value="$well->latitude" />
<x-well-field name="longitude" label="Długość geograficzna" :value="$well->longitude" />
<x-well-select name="sys_type" label="Typ systemu" :options="$sysTypes" :selected="$well->sys_type" :value-is-label="true" placeholder="- wybierz -" />
<x-well-select name="status_desc" label="Opis stanu" :options="$statusDescs" :selected="$well->status_desc" :value-is-label="true" placeholder="- wybierz -" />
<x-well-field name="pp_scope" label="Zakres ciśnienia p_p [m]" :value="$well->pp_scope" />
<x-well-field name="hh_scope" label="Zakres sondy H_H [m]" :value="$well->hh_scope" />
</div>
<p class="font-body-sm text-body-sm text-on-surface-variant">
Uwaga: zakresy p<sub>p</sub>/H<sub>H</sub> są aktualizowane automatycznie po przypisaniu sondy SGP.
Edytuj ręcznie tylko gdy wartość jest pusta.
</p>
<div class="flex items-center gap-2 pt-2">
<button type="submit"
class="bg-primary text-on-primary px-6 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">
Zapisz zmiany
</button>
<a href="{{ route('wells.index') }}"
class="px-5 py-2.5 rounded font-title-sm text-title-sm text-on-surface-variant hover:bg-surface-container transition-all">
Anuluj
</a>
</div>
</form>
</div>
</x-app-layout>
+109
View File
@@ -0,0 +1,109 @@
<x-app-layout :active="'wells.index'" title="SoftSPM 2 - Studnie">
<div class="space-y-stack-gap">
{{-- Komunikat sukcesu --}}
@if (session('status'))
<div class="status-chip-ok px-3 py-2 rounded-lg font-body-sm text-body-sm">
{{ session('status') }}
</div>
@endif
{{-- Nagłówek --}}
<div class="flex items-end justify-between">
<div>
<h1 class="font-headline-lg text-headline-lg text-on-surface">Studnie</h1>
<p class="font-body-sm text-body-sm text-on-surface-variant mt-1">
Oddział: <span class="font-data-label">{{ $department ?? '-' }}</span>
· Wyników: <span class="font-data-label">{{ $wells->total() }}</span>
</p>
</div>
<a href="{{ route('wells.create') }}"
class="bg-primary text-on-primary px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:bg-primary-container transition-all active:scale-95 shadow-card">
Dodaj studnię
</a>
</div>
{{-- Wyszukiwarka (GET - filtry komponują się z paginacją) --}}
<form method="GET" action="{{ route('wells.index') }}"
class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card p-card-inner-padding">
<div class="flex flex-wrap items-end gap-4">
<div class="space-y-1">
<label for="q" class="font-data-label text-data-label text-on-surface-variant uppercase tracking-wider">Studnia</label>
<input id="q" name="q" type="text" value="{{ $search }}"
placeholder="nazwa studni"
class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded focus:ring-1 focus:ring-primary focus:border-primary font-data-label text-on-surface">
</div>
<div class="space-y-1">
<label for="status" class="font-data-label text-data-label text-on-surface-variant uppercase tracking-wider">Status</label>
<select id="status" name="status"
class="bg-surface-container-low border border-outline-variant px-3 py-2 rounded focus:ring-1 focus:ring-primary focus:border-primary font-data-label text-on-surface">
@foreach ($statusOptions as $id => $label)
<option value="{{ $id }}" {{ (int) $status === (int) $id ? 'selected' : '' }}>{{ $label }}</option>
@endforeach
<option value="{{ $statusAll }}" {{ (int) $status === $statusAll ? 'selected' : '' }}>Wszystkie</option>
</select>
</div>
<button type="submit"
class="bg-secondary text-on-secondary px-5 py-2.5 rounded-full font-title-sm text-title-sm hover:opacity-90 transition-all active:scale-95">
Szukaj
</button>
</div>
</form>
{{-- Tabela --}}
<div class="bg-surface-container-lowest border border-outline-variant/40 rounded-lg shadow-card overflow-hidden">
<table class="w-full text-left">
<thead class="bg-secondary/10 border-b border-outline-variant/40">
<tr class="font-data-label text-data-label text-on-surface-variant uppercase tracking-wider">
<th class="px-4 py-3 w-12">Lp.</th>
<x-sortable-th column="name">Nazwa</x-sortable-th>
<x-sortable-th column="bariera">Bariera</x-sortable-th>
<x-sortable-th column="pump">Pompa</x-sortable-th>
<x-sortable-th column="pump_no">Nr pompy</x-sortable-th>
<x-sortable-th column="engine">Silnik</x-sortable-th>
<x-sortable-th column="engine_no">Nr silnika</x-sortable-th>
<x-sortable-th column="type">Typ</x-sortable-th>
<th class="px-4 py-3 text-center">Stan</th>
<th class="px-4 py-3 text-right">Akcje</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/30 font-body-md text-body-md">
@forelse ($wells as $well)
<tr class="hover:bg-surface-container transition-colors">
<td class="px-4 py-3 font-data-label text-on-surface-variant">{{ $wells->firstItem() + $loop->index }}.</td>
<td class="px-4 py-3 font-semibold text-on-surface">{{ $well->well_name }}</td>
<td class="px-4 py-3">{{ $well->bariera }}</td>
<td class="px-4 py-3">{{ $well->pump_name }}</td>
<td class="px-4 py-3 font-data-label">{{ $well->pump_no }}</td>
<td class="px-4 py-3">{{ $well->engine_name }}</td>
<td class="px-4 py-3 font-data-label">{{ $well->engine_no }}</td>
<td class="px-4 py-3">{{ $well->sys_type }}</td>
<td class="px-4 py-3 text-center">
@if ($well->onoff === 'ON')
<span class="status-chip status-chip-ok">ON</span>
@else
<span class="status-chip bg-surface-container-high text-on-surface-variant">OFF</span>
@endif
</td>
<td class="px-4 py-3 text-right">
<a href="{{ route('wells.edit', $well->drs_id) }}"
class="font-body-sm text-body-sm text-secondary hover:underline">Edytuj</a>
</td>
</tr>
@empty
<tr>
<td colspan="10" class="px-4 py-8 text-center font-body-md text-body-md text-on-surface-variant">
Brak studni dla wybranych kryteriów.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{-- Paginacja --}}
<div>
{{ $wells->links() }}
</div>
</div>
</x-app-layout>