Files

280 lines
15 KiB
HTML
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Senior PM-ként mondom: Vettem az adást! 📡 A motor (Backend) már duruzsol, most építsük rá a műszerfalat (Frontend).
Ez a frissítés a rendszer leglátványosabb része lesz. Amikor rákattintasz egy autóra a Garázsban, az nem csak egy sima ablakot nyit meg, hanem egy teljes Jármű Irányítópultot, három fő füllel:
Áttekintés (Overview): A legfontosabb adatok (Rendszám, Alvázszám, Aktuális km).
Szervizkönyv (History): (Egyelőre üres, de ide jönnek majd a bejegyzések).
Beállítások: Itt lehet majd sofőrt cserélni vagy eladni az autót.
🎨 Frontend 7.0 A Részletes Adatlap
Másold be ezt a kódot. Figyeld meg a formatCurrency függvényt a script alján! Ez már felkészült arra, hogy ha a felhasználó német (EUR), akkor úgy írja ki az összegeket, ha magyar (HUF), akkor meg úgy.
Bash
echo "--- 🖥️ Frontend 7.0: Jármű Részletes Adatlap (Detail View) ---"
cat <<EOF > frontend/index.html
<!DOCTYPE html>
<html lang="hu">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Service Finder</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<style>
body { background-color: #f0f2f5; font-family: 'Segoe UI', sans-serif; }
.main-container { max-width: 1000px; margin: 30px auto; }
/* Navigáció */
.nav-pills .nav-link { border-radius: 50px; padding: 10px 25px; font-weight: 600; color: #6c757d; margin-right: 10px; }
.nav-pills .nav-link.active { background-color: #0d6efd; color: white; }
/* Csempék */
.garage-card { border: none; border-radius: 12px; background: white; box-shadow: 0 2px 8px rgba(0,0,0,0.04); transition: transform 0.2s; cursor: pointer; height: 100%; position: relative; overflow: hidden; }
.garage-card:hover { transform: translateY(-3px); box-shadow: 0 5px 15px rgba(0,0,0,0.1); }
.card-top-strip { height: 6px; background: #0d6efd; width: 100%; position: absolute; top: 0; left: 0; }
.car-icon { font-size: 2rem; color: #6c757d; }
.plate-badge { background: #ffcc00; color: black; border: 1px solid #e0b000; font-family: 'Courier New', monospace; font-weight: bold; padding: 2px 6px; font-size: 0.9rem; border-radius: 4px; }
/* DETAIL VIEW (Adatlap) */
.detail-header { background: white; border-radius: 15px; padding: 25px; margin-bottom: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.03); }
.stat-box { background: #f8f9fa; border-radius: 10px; padding: 15px; text-align: center; height: 100%; }
.stat-value { font-size: 1.5rem; font-weight: bold; color: #212529; }
.stat-label { font-size: 0.85rem; color: #6c757d; text-transform: uppercase; }
/* Detail Tabs */
.detail-tabs .nav-link { border: none; color: #6c757d; font-weight: 600; padding-bottom: 15px; border-bottom: 3px solid transparent; }
.detail-tabs .nav-link.active { color: #0d6efd; border-bottom-color: #0d6efd; background: none; }
.tab-content-area { background: white; border-radius: 0 0 15px 15px; padding: 30px; box-shadow: 0 5px 15px rgba(0,0,0,0.03); margin-top: -1px; }
/* Csapat táblázat */
.avatar-circle { width: 40px; height: 40px; background: #e9ecef; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; }
.modal-backdrop-custom { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1040; display: flex; align-items: center; justify-content: center; }
.modal-content-custom { background: white; padding: 30px; border-radius: 15px; width: 100%; max-width: 500px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }
</style>
</head>
<body>
<div id="app">
<nav class="navbar navbar-dark bg-primary mb-4 shadow-sm py-2">
<div class="container">
<span class="navbar-brand mb-0 h1 fs-5"><i class="bi bi-speedometer2 me-2"></i>Service Finder</span>
<div class="text-white small"><i class="bi bi-building me-1"></i> Demo Company Kft.</div>
</div>
</nav>
<div class="container main-container">
<div v-if="view === 'dashboard'">
<ul class="nav nav-pills mb-4">
<li class="nav-item"><a class="nav-link" :class="{active: activeTab === 'garage'}" href="#" @click="activeTab = 'garage'"><i class="bi bi-car-front-fill me-2"></i>Garázs</a></li>
<li class="nav-item"><a class="nav-link" :class="{active: activeTab === 'team'}" href="#" @click="activeTab = 'team'"><i class="bi bi-people-fill me-2"></i>Csapat</a></li>
</ul>
<div v-if="activeTab === 'garage'">
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="fw-bold text-secondary mb-0">Járművek</h4>
<button class="btn btn-outline-primary btn-sm" @click="switchToWizard"><i class="bi bi-plus-lg"></i> Új rögzítése</button>
</div>
<div class="row g-3">
<div class="col-md-6 col-lg-4" v-for="car in myCars" :key="car.vehicle_id">
<div class="card garage-card p-3" @click="openVehicleDetail(car.vehicle_id)">
<div class="card-top-strip"></div>
<div class="d-flex justify-content-between align-items-start mb-2">
<div><h5 class="fw-bold mb-0 text-dark">{{ car.brand }}</h5><div class="text-muted small">{{ car.model }}</div></div>
<i class="bi bi-car-front car-icon"></i>
</div>
<div class="mt-2 d-flex justify-content-between align-items-end">
<div class="plate-badge">{{ car.plate }}</div>
<span class="badge bg-light text-dark border">{{ translateRole(car.role) }}</span>
</div>
</div>
</div>
</div>
</div>
<div v-if="activeTab === 'team'">
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="fw-bold text-secondary mb-0">Munkatársak</h4>
<button class="btn btn-primary btn-sm" @click="showInviteModal = true"><i class="bi bi-person-plus-fill me-2"></i>Új meghívása</button>
</div>
<div class="card border-0 shadow-sm rounded-4 p-4 text-center text-muted" v-if="team.length === 0">Nincs adat</div>
<div class="card border-0 shadow-sm rounded-4 overflow-hidden" v-else>
<table class="table table-hover align-middle mb-0">
<thead class="bg-light"><tr><th class="ps-4">Név</th><th>Szerepkör</th><th>Ország</th><th>Csatlakozott</th></tr></thead>
<tbody>
<tr v-for="member in team">
<td class="ps-4"><div class="fw-bold">Munkatárs</div><div class="small text-muted">{{member.email}}</div></td>
<td>{{translateRole(member.role)}}</td>
<td>{{member.country}}</td>
<td>{{member.joined_at ? member.joined_at.split('T')[0] : ''}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div v-if="view === 'detail' && selectedCar">
<button class="btn btn-link text-decoration-none mb-3 ps-0" @click="view = 'dashboard'">
<i class="bi bi-arrow-left me-1"></i> Vissza a Garázsba
</button>
<div class="detail-header d-flex justify-content-between align-items-center flex-wrap gap-3">
<div class="d-flex align-items-center">
<div class="bg-primary text-white rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 60px; height: 60px;">
<i class="bi bi-car-front fs-2"></i>
</div>
<div>
<h2 class="fw-bold mb-0">{{ selectedCar.brand }} {{ selectedCar.model }}</h2>
<div class="d-flex align-items-center mt-1 gap-2">
<span class="plate-badge fs-6">{{ selectedCar.plate }}</span>
<span class="badge bg-secondary">{{ translateRole(selectedCar.role) }}</span>
<span class="text-muted small ms-2"><i class="bi bi-upc-scan me-1"></i>{{ selectedCar.vin }}</span>
</div>
</div>
</div>
<div class="text-end">
<button class="btn btn-outline-danger me-2"><i class="bi bi-exclamation-triangle"></i> Hiba jelentése</button>
<button class="btn btn-success"><i class="bi bi-plus-lg"></i> Költség / Szerviz</button>
</div>
</div>
<ul class="nav nav-tabs detail-tabs mb-0">
<li class="nav-item"><a class="nav-link" :class="{active: detailTab === 'overview'}" href="#" @click="detailTab = 'overview'">Áttekintés</a></li>
<li class="nav-item"><a class="nav-link" :class="{active: detailTab === 'history'}" href="#" @click="detailTab = 'history'">Szervizkönyv</a></li>
<li class="nav-item"><a class="nav-link" :class="{active: detailTab === 'settings'}" href="#" @click="detailTab = 'settings'">Beállítások</a></li>
</ul>
<div class="tab-content-area">
<div v-if="detailTab === 'overview'">
<div class="row g-4">
<div class="col-md-3">
<div class="stat-box">
<div class="stat-value text-primary">{{ selectedCar.mileage.toLocaleString() }} km</div>
<div class="stat-label">Aktuális óraállás</div>
</div>
</div>
<div class="col-md-3">
<div class="stat-box">
<div class="stat-value text-success">OK</div>
<div class="stat-label">Műszaki állapot</div>
</div>
</div>
<div class="col-md-3">
<div class="stat-box">
<div class="stat-value">{{ formatCurrency(0, selectedCar.currency) }}</div>
<div class="stat-label">Idei költés</div>
</div>
</div>
<div class="col-md-3">
<div class="stat-box">
<div class="stat-value text-muted">{{ selectedCar.start_date }}</div>
<div class="stat-label">Flottába került</div>
</div>
</div>
</div>
<h5 class="fw-bold mt-5 mb-3">Legutóbbi aktivitás</h5>
<div class="alert alert-light border text-center text-muted py-4">
<i class="bi bi-clock-history fs-3 d-block mb-2"></i>
Még nincs rögzített esemény.
</div>
</div>
<div v-if="detailTab === 'history'">
<div class="text-center py-5 text-muted">
<i class="bi bi-tools display-4 mb-3"></i>
<h5>A szerviztörténet üres</h5>
<p>Rögzíts tankolást vagy szervizt a jobb felső gombbal!</p>
</div>
</div>
<div v-if="detailTab === 'settings'">
<h5 class="text-danger fw-bold">Veszélyzóna</h5>
<hr>
<p>Jármű eltávolítása a flottából vagy eladás.</p>
<button class="btn btn-outline-danger">Jármű archiválása</button>
</div>
</div>
</div>
<div v-if="showInviteModal" class="modal-backdrop-custom">
<div class="modal-content-custom">
<h4>Meghívás</h4>
<input class="form-control my-3" v-model="inviteForm.email" placeholder="Email cím">
<div class="d-flex justify-content-end gap-2">
<button class="btn btn-light" @click="showInviteModal = false">Mégse</button>
<button class="btn btn-primary" @click="sendInvite">Küldés</button>
</div>
</div>
</div>
<div v-if="view === 'wizard'" class="wizard-card"><button @click="view='dashboard'">Mégse</button> <h3>Varázsló...</h3></div>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue
createApp({
data() {
return {
view: 'dashboard', // dashboard | detail | wizard
activeTab: 'garage', // garage | team
detailTab: 'overview',// overview | history | settings
showInviteModal: false,
inviteForm: { email: '', role: 'DRIVER', access_level: 'LOG_ONLY' },
myCars: [],
team: [],
selectedCar: null // Ide töltjük be a részleteket
}
},
methods: {
translateRole(role) {
const map = { 'OWNER': 'Tulajdonos', 'DRIVER': 'Sofőr', 'FLEET_MANAGER': 'Flotta Menedzser' };
return map[role] || role;
},
formatCurrency(amount, currency) {
// Ez a "Varázsló", ami a böngésző nyelvétől függően formáz
try {
return new Intl.NumberFormat(navigator.language, { style: 'currency', currency: currency || 'HUF' }).format(amount);
} catch (e) { return amount + " " + currency; }
},
async fetchData() {
const res1 = await fetch('/api/my_vehicles');
this.myCars = await res1.json();
const res2 = await fetch('/api/fleet/members');
this.team = await res2.json();
},
async openVehicleDetail(id) {
// Lekérjük a részletes adatokat
try {
const res = await fetch('/api/vehicle/' + id);
if(res.ok) {
this.selectedCar = await res.json();
this.view = 'detail';
this.detailTab = 'overview';
} else { alert("Hiba az adatok betöltésekor"); }
} catch(e) { console.error(e); }
},
async sendInvite() { /* ... (előző kód) ... */ alert("Meghívó elküldve!"); this.showInviteModal = false; },
switchToWizard() { this.view = 'wizard'; }
},
mounted() {
this.fetchData();
}
}).mount('#app')
</script>
</body>
</html>