6205 lines
277 KiB
HTML
6205 lines
277 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>24HRS - Messe-Lotse</title>
|
||
|
||
<!-- Google Fonts -->
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap" rel="stylesheet">
|
||
|
||
<!-- Font Awesome -->
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||
|
||
<!-- QRCode.js Library -->
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
|
||
|
||
<style>
|
||
:root {
|
||
--primary-yellow: #FFE000;
|
||
--primary-black: #111111;
|
||
--bg-light: #F5F5F5;
|
||
--white: #FFFFFF;
|
||
--text-primary: #111111;
|
||
--text-secondary: #555555;
|
||
--border-color: #E0E0E0;
|
||
--hover-yellow: #FFC800;
|
||
}
|
||
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Inter', sans-serif;
|
||
background-color: var(--bg-light);
|
||
color: var(--text-primary);
|
||
font-size: 14px;
|
||
}
|
||
|
||
.app {
|
||
display: flex;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
/* Sidebar */
|
||
.sidebar {
|
||
width: 260px;
|
||
background-color: var(--primary-black);
|
||
color: var(--white);
|
||
padding: 24px;
|
||
position: fixed;
|
||
height: 100vh;
|
||
}
|
||
|
||
.logo {
|
||
font-weight: 800;
|
||
font-size: 1.8rem;
|
||
color: var(--primary-yellow);
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.subtitle {
|
||
font-size: 0.7rem;
|
||
color: var(--primary-yellow);
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.1em;
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
.nav-item {
|
||
padding: 14px;
|
||
color: var(--white);
|
||
cursor: pointer;
|
||
margin-bottom: 8px;
|
||
border-radius: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
font-weight: 600;
|
||
font-size: 0.85rem;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.nav-item:hover {
|
||
background-color: #333;
|
||
}
|
||
|
||
.nav-item.active {
|
||
background-color: var(--primary-yellow);
|
||
color: var(--primary-black);
|
||
}
|
||
|
||
/* Main Content */
|
||
.main-content {
|
||
flex: 1;
|
||
margin-left: 260px;
|
||
padding: 32px;
|
||
}
|
||
|
||
h1 {
|
||
font-size: 2rem;
|
||
font-weight: 800;
|
||
text-transform: uppercase;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.subtitle-text {
|
||
color: var(--text-secondary);
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
/* Stats */
|
||
.stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 20px;
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
.stat-card {
|
||
background: var(--primary-black);
|
||
color: var(--white);
|
||
padding: 24px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.stat-icon {
|
||
font-size: 2rem;
|
||
color: var(--primary-yellow);
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 0.7rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.1em;
|
||
margin-bottom: 8px;
|
||
color: #999;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 2.5rem;
|
||
font-weight: 800;
|
||
color: var(--primary-yellow);
|
||
}
|
||
|
||
/* Buttons */
|
||
.btn {
|
||
padding: 12px 24px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-family: 'Inter', sans-serif;
|
||
font-size: 0.85rem;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: var(--primary-yellow);
|
||
color: var(--primary-black);
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: var(--hover-yellow);
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: var(--primary-black);
|
||
color: var(--white);
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: #333;
|
||
}
|
||
|
||
/* Toolbar */
|
||
.toolbar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 24px;
|
||
gap: 16px;
|
||
}
|
||
|
||
.search-box input {
|
||
padding: 12px 16px;
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 4px;
|
||
width: 300px;
|
||
font-family: 'Inter', sans-serif;
|
||
}
|
||
|
||
/* Jobs Grid */
|
||
.jobs-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||
gap: 24px;
|
||
}
|
||
|
||
.job-card {
|
||
background: var(--white);
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||
border-top: 4px solid var(--primary-yellow);
|
||
}
|
||
|
||
.job-card:hover {
|
||
transform: translateY(-4px);
|
||
box-shadow: 0 8px 16px rgba(0,0,0,0.12);
|
||
}
|
||
|
||
.job-card-header {
|
||
padding: 20px;
|
||
border-bottom: 1px solid var(--border-color);
|
||
}
|
||
|
||
.job-id {
|
||
font-size: 0.7rem;
|
||
text-transform: uppercase;
|
||
color: var(--text-secondary);
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.job-title {
|
||
font-size: 1.2rem;
|
||
font-weight: 800;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.job-subtitle {
|
||
color: var(--text-secondary);
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.job-card-body {
|
||
padding: 20px;
|
||
}
|
||
|
||
.meta-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 10px;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.meta-item i {
|
||
color: var(--primary-yellow);
|
||
}
|
||
|
||
.progress-bar-container {
|
||
background: var(--border-color);
|
||
height: 8px;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.progress-bar {
|
||
background: var(--primary-yellow);
|
||
height: 100%;
|
||
transition: width 0.3s;
|
||
}
|
||
|
||
/* Empty State */
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 80px 20px;
|
||
}
|
||
|
||
.empty-state i {
|
||
font-size: 4rem;
|
||
color: var(--border-color);
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.hidden {
|
||
display: none;
|
||
}
|
||
|
||
/* Tabs */
|
||
.tabs {
|
||
background: white;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.tab-nav {
|
||
display: flex;
|
||
border-bottom: 2px solid var(--border-color);
|
||
background: var(--white);
|
||
}
|
||
|
||
.tab-btn {
|
||
flex: 1;
|
||
padding: 16px 24px;
|
||
background: transparent;
|
||
border: none;
|
||
font-family: 'Inter', sans-serif;
|
||
font-size: 0.85rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
color: var(--text-secondary);
|
||
cursor: pointer;
|
||
position: relative;
|
||
transition: all 0.2s;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.tab-btn:hover {
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.tab-btn.active {
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.tab-btn.active::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: -2px;
|
||
left: 0;
|
||
right: 0;
|
||
height: 3px;
|
||
background: var(--primary-yellow);
|
||
}
|
||
|
||
.tab-content {
|
||
padding: 32px;
|
||
}
|
||
|
||
.tab-pane {
|
||
display: none;
|
||
}
|
||
|
||
.tab-pane.active {
|
||
display: block;
|
||
}
|
||
|
||
/* Form Styles */
|
||
.form-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 20px;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.form-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.form-group.full-width {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
label {
|
||
font-size: 0.75rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
margin-bottom: 8px;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
input, select, textarea {
|
||
padding: 12px 16px;
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 4px;
|
||
font-family: 'Inter', sans-serif;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
input:focus, select:focus, textarea:focus {
|
||
outline: none;
|
||
border-color: var(--primary-yellow);
|
||
}
|
||
|
||
textarea {
|
||
min-height: 100px;
|
||
resize: vertical;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 1.1rem;
|
||
font-weight: 800;
|
||
text-transform: uppercase;
|
||
margin: 32px 0 16px;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
/* Dynamic Lists */
|
||
.list-item {
|
||
background: var(--bg-light);
|
||
padding: 16px;
|
||
border-radius: 4px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.list-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.btn-small {
|
||
padding: 8px 16px;
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.icon-btn {
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--text-secondary);
|
||
cursor: pointer;
|
||
padding: 8px;
|
||
border-radius: 4px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.icon-btn:hover {
|
||
background: var(--border-color);
|
||
}
|
||
|
||
.icon-btn.danger:hover {
|
||
background: #dc3545;
|
||
color: white;
|
||
}
|
||
|
||
/* Checklist */
|
||
.checklist-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px;
|
||
background: var(--bg-light);
|
||
border-radius: 4px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.checklist-item input[type="checkbox"] {
|
||
width: 20px;
|
||
height: 20px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.checklist-item label {
|
||
flex: 1;
|
||
margin: 0;
|
||
font-size: 0.9rem;
|
||
font-weight: 400;
|
||
text-transform: none;
|
||
letter-spacing: normal;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.checklist-item.completed label {
|
||
text-decoration: line-through;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
/* Toast */
|
||
.toast {
|
||
position: fixed;
|
||
bottom: 24px;
|
||
right: 24px;
|
||
background: var(--primary-black);
|
||
color: var(--white);
|
||
padding: 16px 24px;
|
||
border-radius: 4px;
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||
display: none;
|
||
align-items: center;
|
||
gap: 12px;
|
||
border-left: 4px solid var(--primary-yellow);
|
||
z-index: 1000;
|
||
}
|
||
|
||
.toast.show {
|
||
display: flex;
|
||
animation: slideIn 0.3s;
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from {
|
||
transform: translateX(400px);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
/* Gallery Styles */
|
||
.gallery-upload {
|
||
background: var(--bg-light);
|
||
padding: 24px;
|
||
border-radius: 4px;
|
||
border: 2px dashed #CCCCCC;
|
||
text-align: center;
|
||
margin-bottom: 24px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.gallery-upload:hover {
|
||
border-color: var(--primary-yellow);
|
||
background: #FAFAFA;
|
||
}
|
||
|
||
.gallery-upload input[type="file"] {
|
||
display: none;
|
||
}
|
||
|
||
.gallery-upload-label {
|
||
display: inline-block;
|
||
padding: 12px 24px;
|
||
background: var(--primary-yellow);
|
||
color: var(--primary-black);
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.gallery-upload-label:hover {
|
||
background: var(--hover-yellow);
|
||
}
|
||
|
||
.gallery-upload-info {
|
||
margin-top: 12px;
|
||
color: var(--text-secondary);
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
.gallery-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||
gap: 16px;
|
||
margin-top: 24px;
|
||
}
|
||
|
||
.gallery-item {
|
||
position: relative;
|
||
background: var(--white);
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
transition: all 0.2s;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.gallery-item:hover {
|
||
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.gallery-item-image {
|
||
width: 100%;
|
||
height: 200px;
|
||
object-fit: cover;
|
||
display: block;
|
||
}
|
||
|
||
.gallery-item-info {
|
||
padding: 12px;
|
||
background: var(--white);
|
||
}
|
||
|
||
.gallery-item-name {
|
||
font-size: 0.85rem;
|
||
color: var(--text-primary);
|
||
font-weight: 600;
|
||
margin-bottom: 8px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.gallery-item-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.gallery-item-btn {
|
||
flex: 1;
|
||
padding: 6px 12px;
|
||
background: var(--bg-light);
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.gallery-item-btn:hover {
|
||
background: #E0E0E0;
|
||
}
|
||
|
||
.gallery-item-btn.delete {
|
||
color: #dc3545;
|
||
}
|
||
|
||
.gallery-item-btn.delete:hover {
|
||
background: #dc3545;
|
||
color: white;
|
||
}
|
||
|
||
/* Lightbox */
|
||
.lightbox {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0,0,0,0.9);
|
||
z-index: 10000;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.lightbox.active {
|
||
display: flex;
|
||
}
|
||
|
||
.lightbox-content {
|
||
position: relative;
|
||
max-width: 90%;
|
||
max-height: 90%;
|
||
}
|
||
|
||
.lightbox-image {
|
||
max-width: 100%;
|
||
max-height: 90vh;
|
||
object-fit: contain;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.lightbox-close {
|
||
position: absolute;
|
||
top: -40px;
|
||
right: 0;
|
||
background: var(--primary-yellow);
|
||
color: var(--primary-black);
|
||
border: none;
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 4px;
|
||
font-size: 1.5rem;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.lightbox-close:hover {
|
||
background: var(--hover-yellow);
|
||
}
|
||
|
||
.lightbox-nav {
|
||
position: absolute;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
background: var(--primary-yellow);
|
||
color: var(--primary-black);
|
||
border: none;
|
||
width: 50px;
|
||
height: 50px;
|
||
border-radius: 4px;
|
||
font-size: 1.5rem;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.lightbox-nav:hover {
|
||
background: var(--hover-yellow);
|
||
}
|
||
|
||
.lightbox-nav.prev {
|
||
left: -60px;
|
||
}
|
||
|
||
.lightbox-nav.next {
|
||
right: -60px;
|
||
}
|
||
|
||
.gallery-empty {
|
||
text-align: center;
|
||
padding: 48px 24px;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.gallery-empty i {
|
||
font-size: 3rem;
|
||
color: #CCCCCC;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
/* Calendar Styles */
|
||
.calendar-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
gap: 1px;
|
||
background: #E0E0E0;
|
||
border: 1px solid #E0E0E0;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.calendar-day-header {
|
||
background: var(--primary-black);
|
||
color: white;
|
||
padding: 12px;
|
||
text-align: center;
|
||
font-weight: 700;
|
||
font-size: 0.75rem;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.calendar-day {
|
||
background: white;
|
||
min-height: 100px;
|
||
padding: 8px;
|
||
position: relative;
|
||
}
|
||
|
||
.calendar-day.other-month {
|
||
background: #F9F9F9;
|
||
color: #CCCCCC;
|
||
}
|
||
|
||
.calendar-day.today {
|
||
background: #FFF9E6;
|
||
border: 2px solid var(--primary-yellow);
|
||
}
|
||
|
||
.calendar-day-number {
|
||
font-weight: 600;
|
||
font-size: 0.85rem;
|
||
margin-bottom: 4px;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.calendar-event {
|
||
background: #3498db;
|
||
color: white;
|
||
padding: 4px 6px;
|
||
border-radius: 3px;
|
||
font-size: 0.7rem;
|
||
margin-bottom: 2px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.calendar-event:hover {
|
||
opacity: 0.8;
|
||
transform: translateX(2px);
|
||
}
|
||
|
||
.calendar-event.setup {
|
||
background: #3498db;
|
||
}
|
||
|
||
.calendar-event.event {
|
||
background: var(--primary-yellow);
|
||
color: var(--primary-black);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.calendar-event.teardown {
|
||
background: #e74c3c;
|
||
}
|
||
|
||
.calendar-event.deadline {
|
||
background: #FF6B35;
|
||
color: white;
|
||
font-weight: 600;
|
||
border-left: 3px solid #C44D2C;
|
||
}
|
||
|
||
.calendar-event-badge {
|
||
display: inline-block;
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
margin-right: 4px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="app">
|
||
<!-- Sidebar -->
|
||
<nav class="sidebar">
|
||
<div class="logo">24HRS</div>
|
||
<div class="subtitle">Messe-Lotse</div>
|
||
|
||
<div class="nav-item active" id="nav-dashboard">
|
||
<i class="fas fa-th-large"></i>
|
||
<span>Dashboard</span>
|
||
</div>
|
||
|
||
<div class="nav-item" id="nav-calendar">
|
||
<i class="fas fa-calendar-alt"></i>
|
||
<span>Kalender</span>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- Main Content -->
|
||
<main class="main-content">
|
||
<h1>Dashboard</h1>
|
||
<p class="subtitle-text">Übersicht aller Messeprojekte</p>
|
||
|
||
<!-- Stats -->
|
||
<div class="stats-grid">
|
||
<div class="stat-card">
|
||
<div class="stat-icon"><i class="fas fa-briefcase"></i></div>
|
||
<div class="stat-label">Gesamt</div>
|
||
<div class="stat-value" id="stat-total">0</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="stat-icon"><i class="fas fa-clock"></i></div>
|
||
<div class="stat-label">Offen</div>
|
||
<div class="stat-value" id="stat-open">0</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="stat-icon"><i class="fas fa-calendar-week"></i></div>
|
||
<div class="stat-label">Diese Woche</div>
|
||
<div class="stat-value" id="stat-week">0</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Toolbar -->
|
||
<div class="toolbar">
|
||
<div class="search-box">
|
||
<input type="text" id="search-input" placeholder="Jobs durchsuchen...">
|
||
</div>
|
||
|
||
<button class="btn btn-primary" id="new-job-btn">
|
||
<i class="fas fa-plus"></i> Neuer Job
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Dashboard View -->
|
||
<div id="dashboard-view">
|
||
<!-- Jobs Grid -->
|
||
<div id="jobs-container" class="jobs-grid"></div>
|
||
|
||
<!-- Empty State -->
|
||
<div id="empty-state" class="empty-state">
|
||
<i class="fas fa-briefcase"></i>
|
||
<h3>Keine Jobs vorhanden</h3>
|
||
<p>Erstellen Sie Ihren ersten Messejob</p>
|
||
<button class="btn btn-primary" id="empty-new-job-btn">
|
||
<i class="fas fa-plus"></i> Neuer Job
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Calendar View -->
|
||
<div id="calendar-view" class="hidden">
|
||
<h1>Kalender</h1>
|
||
<p class="subtitle-text">Zeitplanung aller Messeprojekte</p>
|
||
|
||
<!-- Calendar Toolbar -->
|
||
<div class="calendar-toolbar" style="background: white; padding: 16px; border-radius: 4px; margin-bottom: 24px; display: flex; justify-content: space-between; align-items: center;">
|
||
<div style="display: flex; gap: 16px; align-items: center;">
|
||
<button class="btn btn-secondary btn-small" id="calendar-prev-month">
|
||
<i class="fas fa-chevron-left"></i>
|
||
</button>
|
||
<h2 id="calendar-month-year" style="margin: 0; font-size: 1.2rem;">Januar 2026</h2>
|
||
<button class="btn btn-secondary btn-small" id="calendar-next-month">
|
||
<i class="fas fa-chevron-right"></i>
|
||
</button>
|
||
<button class="btn btn-secondary btn-small" id="calendar-today">
|
||
Heute
|
||
</button>
|
||
</div>
|
||
<div style="display: flex; gap: 8px; align-items: center;">
|
||
<div style="display: flex; align-items: center; gap: 4px; font-size: 0.85rem;">
|
||
<span style="display: inline-block; width: 12px; height: 12px; background: #3498db; border-radius: 2px;"></span>
|
||
<span>Aufbau</span>
|
||
</div>
|
||
<div style="display: flex; align-items: center; gap: 4px; font-size: 0.85rem;">
|
||
<span style="display: inline-block; width: 12px; height: 12px; background: #FFE000; border-radius: 2px;"></span>
|
||
<span>Messe</span>
|
||
</div>
|
||
<div style="display: flex; align-items: center; gap: 4px; font-size: 0.85rem;">
|
||
<span style="display: inline-block; width: 12px; height: 12px; background: #e74c3c; border-radius: 2px;"></span>
|
||
<span>Abbau</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Calendar Container -->
|
||
<div id="calendar-container" style="background: white; padding: 24px; border-radius: 4px;">
|
||
<!-- Calendar will be rendered here -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Job Detail View -->
|
||
<div id="job-detail-view" class="hidden">
|
||
<div style="background: var(--primary-black); color: white; padding: 24px; border-radius: 4px; margin-bottom: 24px; display: flex; justify-content: space-between; align-items: center;">
|
||
<div>
|
||
<button class="btn btn-secondary" id="back-btn" style="margin-bottom: 16px;">
|
||
<i class="fas fa-arrow-left"></i> Zurück
|
||
</button>
|
||
<h2 id="job-detail-title">Job Details</h2>
|
||
</div>
|
||
<div style="display: flex; gap: 12px;">
|
||
<button class="btn btn-primary" id="save-job-btn">
|
||
<i class="fas fa-save"></i> Speichern
|
||
</button>
|
||
<button class="btn btn-secondary" id="duplicate-job-btn">
|
||
<i class="fas fa-copy"></i> Duplizieren
|
||
</button>
|
||
<button class="btn btn-secondary" id="export-job-btn">
|
||
<i class="fas fa-file-pdf"></i> PDF Export
|
||
</button>
|
||
<button class="btn btn-secondary" id="delete-job-btn">
|
||
<i class="fas fa-trash"></i> Löschen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tabs -->
|
||
<div class="tabs">
|
||
<div class="tab-nav">
|
||
<button class="tab-btn active" data-tab="0">
|
||
<i class="fas fa-info-circle"></i> Stammdaten & Termine
|
||
</button>
|
||
<button class="tab-btn" data-tab="1">
|
||
<i class="fas fa-file-alt"></i> Briefing
|
||
</button>
|
||
<button class="tab-btn" data-tab="2">
|
||
<i class="fas fa-truck"></i> Organisation
|
||
</button>
|
||
<button class="tab-btn" data-tab="3">
|
||
<i class="fas fa-tools"></i> Produktion
|
||
</button>
|
||
<button class="tab-btn" data-tab="4">
|
||
<i class="fas fa-tasks"></i> Dokumente
|
||
</button>
|
||
<button class="tab-btn" data-tab="5">
|
||
<i class="fas fa-images"></i> Standfotos
|
||
</button>
|
||
<button class="tab-btn" data-tab="6">
|
||
<i class="fas fa-clipboard-check"></i> Standabnahme
|
||
</button>
|
||
<button class="tab-btn" data-tab="7">
|
||
<i class="fas fa-calendar-week"></i> Weekly
|
||
</button>
|
||
</div>
|
||
|
||
<div class="tab-content">
|
||
<!-- Tab 1: Stammdaten & Termine -->
|
||
<div class="tab-pane active" id="tab-0">
|
||
<h3 class="section-title">Stammdaten</h3>
|
||
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>Kunde</label>
|
||
<input type="text" id="edit-customer">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Messe / Event</label>
|
||
<input type="text" id="edit-event">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Ort</label>
|
||
<div style="display: flex; gap: 8px; align-items: center;">
|
||
<input type="text" id="edit-location" style="flex: 1;">
|
||
<button type="button" class="btn btn-secondary btn-small" onclick="openGoogleMaps()" title="In Google Maps öffnen">
|
||
<i class="fas fa-map-marker-alt"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Projekt</label>
|
||
<input type="text" id="edit-project">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Standart</label>
|
||
<select id="edit-stand-type">
|
||
<option value="">Bitte wählen</option>
|
||
<option value="Systemstand">Systemstand</option>
|
||
<option value="Reihstand">Reihstand</option>
|
||
<option value="Eckstand">Eckstand</option>
|
||
<option value="Kopfstand">Kopfstand</option>
|
||
<option value="Blockstand">Blockstand</option>
|
||
<option value="Inselstand">Inselstand</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Standgröße (B×T in m)</label>
|
||
<input type="text" id="edit-stand-size" placeholder="z.B. 6x4">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Standhöhe (m)</label>
|
||
<input type="number" step="0.1" id="edit-stand-height" placeholder="z.B. 2.5">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Halle / Stand</label>
|
||
<input type="text" id="edit-hall">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Standfläche (m²)</label>
|
||
<input type="number" id="edit-area">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Status</label>
|
||
<select id="edit-status">
|
||
<option value="Offen">Offen</option>
|
||
<option value="In Bearbeitung">In Bearbeitung</option>
|
||
<option value="Abgeschlossen">Abgeschlossen</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<h3 class="section-title">Zeitplanung</h3>
|
||
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>Aufbau Start</label>
|
||
<input type="datetime-local" id="edit-setup-start">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Aufbau Ende</label>
|
||
<input type="datetime-local" id="edit-setup-end">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Messe Start</label>
|
||
<input type="datetime-local" id="edit-event-start">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Messe Ende</label>
|
||
<input type="datetime-local" id="edit-event-end">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Abbau Start</label>
|
||
<input type="datetime-local" id="edit-teardown-start">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Abbau Ende</label>
|
||
<input type="datetime-local" id="edit-teardown-end">
|
||
</div>
|
||
</div>
|
||
|
||
<h3 class="section-title">Ansprechpartner</h3>
|
||
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>Name</label>
|
||
<input type="text" id="edit-contact-name">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Telefon</label>
|
||
<input type="tel" id="edit-contact-phone">
|
||
</div>
|
||
|
||
<div class="form-group full-width">
|
||
<label>E-Mail</label>
|
||
<input type="email" id="edit-contact-email">
|
||
</div>
|
||
</div>
|
||
|
||
<h3 class="section-title">Messestand-Bild</h3>
|
||
|
||
<div class="form-group">
|
||
<label>Bild vom Messestand hochladen</label>
|
||
<input type="file" id="stand-image-upload" accept="image/*" style="margin-bottom: 12px;">
|
||
|
||
<div id="stand-image-preview" style="display: none; background: var(--bg-light); padding: 16px; border-radius: 4px; text-align: center; margin-top: 12px;">
|
||
<img id="stand-image-preview-img" style="max-width: 100%; max-height: 300px; border-radius: 4px; margin-bottom: 12px;">
|
||
<div style="display: flex; gap: 8px; justify-content: center;">
|
||
<button type="button" class="btn btn-secondary btn-small" id="download-stand-image-btn">
|
||
<i class="fas fa-download"></i> Herunterladen
|
||
</button>
|
||
<button type="button" class="btn btn-secondary btn-small" id="remove-stand-image-btn">
|
||
<i class="fas fa-trash"></i> Entfernen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Notizen / Besonderheiten</label>
|
||
<textarea id="edit-notes"></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab 2: Briefing -->
|
||
<div class="tab-pane" id="tab-1">
|
||
<h3 class="section-title">Briefing</h3>
|
||
|
||
<p style="color: #666; margin-bottom: 32px;">
|
||
Laden Sie alle wichtigen Briefing-Dokumente für das Projekt hoch. Unterstützt werden PDFs und Bilder.
|
||
</p>
|
||
|
||
<!-- 1. Briefing Kunde -->
|
||
<div style="margin-bottom: 32px;">
|
||
<h4 style="font-size: 16px; font-weight: 600; color: #111111; margin-bottom: 12px;">
|
||
<i class="fas fa-file-pdf"></i> Briefing Kunde
|
||
</h4>
|
||
<p style="font-size: 14px; color: #666; margin-bottom: 12px;">
|
||
Kundenbriefing mit Anforderungen, Wünschen und Spezifikationen
|
||
</p>
|
||
|
||
<div class="upload-section">
|
||
<input type="file" id="briefing-customer-upload" accept=".pdf,.doc,.docx,image/*" style="display: none;">
|
||
<label for="briefing-customer-upload" class="btn btn-secondary" style="display: inline-block; cursor: pointer;">
|
||
<i class="fas fa-upload"></i> Briefing hochladen
|
||
</label>
|
||
<span style="font-size: 12px; color: #999; margin-left: 12px;">Max. 10 MB (PDF, Word, Bilder)</span>
|
||
</div>
|
||
|
||
<div id="briefing-customer-preview" style="display: none; margin-top: 16px; padding: 16px; background: #F5F5F5; border-radius: 6px;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<div style="display: flex; align-items: center; gap: 12px;">
|
||
<i class="fas fa-file-pdf" style="font-size: 24px; color: #D32F2F;"></i>
|
||
<div>
|
||
<div style="font-weight: 600; color: #111111;" id="briefing-customer-name">Dokument.pdf</div>
|
||
<div style="font-size: 12px; color: #666;" id="briefing-customer-size">0 KB</div>
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<button class="btn btn-secondary btn-small" onclick="downloadBriefingCustomer()">
|
||
<i class="fas fa-download"></i> Download
|
||
</button>
|
||
<button class="btn btn-secondary btn-small" onclick="removeBriefingCustomer()">
|
||
<i class="fas fa-trash"></i> Löschen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 2. Brandbook / Logo -->
|
||
<div style="margin-bottom: 32px;">
|
||
<h4 style="font-size: 16px; font-weight: 600; color: #111111; margin-bottom: 12px;">
|
||
<i class="fas fa-palette"></i> Brandbook / Logo
|
||
</h4>
|
||
<p style="font-size: 14px; color: #666; margin-bottom: 12px;">
|
||
Corporate Identity, Brandbook, Logo-Dateien
|
||
</p>
|
||
|
||
<div class="upload-section">
|
||
<input type="file" id="briefing-brand-upload" accept=".pdf,.ai,.eps,.svg,.png,.jpg,.jpeg" style="display: none;">
|
||
<label for="briefing-brand-upload" class="btn btn-secondary" style="display: inline-block; cursor: pointer;">
|
||
<i class="fas fa-upload"></i> Brandbook hochladen
|
||
</label>
|
||
<span style="font-size: 12px; color: #999; margin-left: 12px;">Max. 10 MB (PDF, AI, EPS, SVG, PNG, JPG)</span>
|
||
</div>
|
||
|
||
<div id="briefing-brand-preview" style="display: none; margin-top: 16px; padding: 16px; background: #F5F5F5; border-radius: 6px;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<div style="display: flex; align-items: center; gap: 12px;">
|
||
<i class="fas fa-palette" style="font-size: 24px; color: #7B1FA2;"></i>
|
||
<div>
|
||
<div style="font-weight: 600; color: #111111;" id="briefing-brand-name">Brandbook.pdf</div>
|
||
<div style="font-size: 12px; color: #666;" id="briefing-brand-size">0 KB</div>
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<button class="btn btn-secondary btn-small" onclick="downloadBriefingBrand()">
|
||
<i class="fas fa-download"></i> Download
|
||
</button>
|
||
<button class="btn btn-secondary btn-small" onclick="removeBriefingBrand()">
|
||
<i class="fas fa-trash"></i> Löschen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 3. Standplan -->
|
||
<div style="margin-bottom: 32px;">
|
||
<h4 style="font-size: 16px; font-weight: 600; color: #111111; margin-bottom: 12px;">
|
||
<i class="fas fa-map"></i> Standplan
|
||
</h4>
|
||
<p style="font-size: 14px; color: #666; margin-bottom: 12px;">
|
||
Technische Zeichnung, Grundriss, Standplan
|
||
</p>
|
||
|
||
<div class="upload-section">
|
||
<input type="file" id="briefing-plan-upload" accept=".pdf,.dwg,.dxf,image/*" style="display: none;">
|
||
<label for="briefing-plan-upload" class="btn btn-secondary" style="display: inline-block; cursor: pointer;">
|
||
<i class="fas fa-upload"></i> Standplan hochladen
|
||
</label>
|
||
<span style="font-size: 12px; color: #999; margin-left: 12px;">Max. 10 MB (PDF, DWG, DXF, Bilder)</span>
|
||
</div>
|
||
|
||
<div id="briefing-plan-preview" style="display: none; margin-top: 16px; padding: 16px; background: #F5F5F5; border-radius: 6px;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<div style="display: flex; align-items: center; gap: 12px;">
|
||
<i class="fas fa-map" style="font-size: 24px; color: #1976D2;"></i>
|
||
<div>
|
||
<div style="font-weight: 600; color: #111111;" id="briefing-plan-name">Standplan.pdf</div>
|
||
<div style="font-size: 12px; color: #666;" id="briefing-plan-size">0 KB</div>
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<button class="btn btn-secondary btn-small" onclick="downloadBriefingPlan()">
|
||
<i class="fas fa-download"></i> Download
|
||
</button>
|
||
<button class="btn btn-secondary btn-small" onclick="removeBriefingPlan()">
|
||
<i class="fas fa-trash"></i> Löschen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 4. Weitere Dokumente -->
|
||
<div style="margin-bottom: 32px;">
|
||
<h4 style="font-size: 16px; font-weight: 600; color: #111111; margin-bottom: 12px;">
|
||
<i class="fas fa-folder-open"></i> Weitere Dokumente
|
||
</h4>
|
||
<p style="font-size: 14px; color: #666; margin-bottom: 12px;">
|
||
Zusätzliche Dateien, Verträge, Spezifikationen
|
||
</p>
|
||
|
||
<div class="upload-section">
|
||
<input type="file" id="briefing-additional-upload" accept="*" style="display: none;">
|
||
<label for="briefing-additional-upload" class="btn btn-secondary" style="display: inline-block; cursor: pointer;">
|
||
<i class="fas fa-upload"></i> Dokument hochladen
|
||
</label>
|
||
<span style="font-size: 12px; color: #999; margin-left: 12px;">Max. 10 MB (alle Dateitypen)</span>
|
||
</div>
|
||
|
||
<div id="briefing-additional-preview" style="display: none; margin-top: 16px; padding: 16px; background: #F5F5F5; border-radius: 6px;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<div style="display: flex; align-items: center; gap: 12px;">
|
||
<i class="fas fa-file" style="font-size: 24px; color: #616161;"></i>
|
||
<div>
|
||
<div style="font-weight: 600; color: #111111;" id="briefing-additional-name">Dokument.pdf</div>
|
||
<div style="font-size: 12px; color: #666;" id="briefing-additional-size">0 KB</div>
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<button class="btn btn-secondary btn-small" onclick="downloadBriefingAdditional()">
|
||
<i class="fas fa-download"></i> Download
|
||
</button>
|
||
<button class="btn btn-secondary btn-small" onclick="removeBriefingAdditional()">
|
||
<i class="fas fa-trash"></i> Löschen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top: 32px; padding: 16px; background: #FFFDE7; border-left: 4px solid #FFE000; border-radius: 4px;">
|
||
<div style="display: flex; gap: 12px;">
|
||
<i class="fas fa-info-circle" style="color: #F57F17; margin-top: 2px;"></i>
|
||
<div style="font-size: 14px; color: #333;">
|
||
<strong>Hinweis:</strong> Alle Dokumente werden verschlüsselt im Browser gespeichert (Base64).
|
||
Beachten Sie das LocalStorage-Limit von ca. 5-10 MB. Bei großen Dateien empfehlen wir Cloud-Speicher.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab 3: Organisation -->
|
||
<div class="tab-pane" id="tab-2">
|
||
<h3 class="section-title">Buchungsplattform</h3>
|
||
|
||
<div class="form-group">
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="edit-booking-platform" onchange="toggleBookingPlatformFields()">
|
||
<label for="edit-booking-platform">Buchungsplattform vorhanden</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="booking-platform-fields" style="display: none; margin-top: 16px;">
|
||
<div class="form-grid">
|
||
<div class="form-group" style="grid-column: 1 / -1;">
|
||
<label>Portal-Link</label>
|
||
<input type="url" id="edit-booking-portal-link" placeholder="https://...">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>E-Mail</label>
|
||
<input type="email" id="edit-booking-email" placeholder="beispiel@firma.de">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Passwort</label>
|
||
<input type="password" id="edit-booking-password" placeholder="••••••••">
|
||
</div>
|
||
|
||
<div class="form-group" style="grid-column: 1 / -1;">
|
||
<label>🗓️ Bestelldeadline</label>
|
||
<input type="date" id="edit-booking-deadline">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3 class="section-title">Strombuchung</h3>
|
||
|
||
<div class="form-group">
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="edit-power-booking" onchange="togglePowerBookingFields()">
|
||
<label for="edit-power-booking">Strom gebucht</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="power-booking-fields" style="display: none; margin-top: 16px;">
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>Leistung (kW)</label>
|
||
<input type="number" id="edit-power-kw" placeholder="z.B. 3.5" step="0.1" min="0">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Stromverteiler</label>
|
||
<input type="text" id="edit-power-distributor" placeholder="z.B. Verteiler A12">
|
||
</div>
|
||
|
||
<div class="form-group" style="grid-column: 1 / -1;">
|
||
<label>🗓️ Bestelldeadline</label>
|
||
<input type="date" id="edit-power-deadline">
|
||
</div>
|
||
|
||
<div class="form-group" style="grid-column: 1 / -1;">
|
||
<label>Buchungsbestätigung (PDF)</label>
|
||
<input type="file" id="edit-power-confirmation-upload" accept=".pdf" onchange="handlePowerConfirmationUpload(event)" style="display: none;">
|
||
<button class="btn btn-secondary btn-small" onclick="document.getElementById('edit-power-confirmation-upload').click()">
|
||
<i class="fas fa-upload"></i> PDF hochladen
|
||
</button>
|
||
|
||
<div id="power-confirmation-preview" style="display: none; margin-top: 12px; padding: 12px; background: #F5F5F5; border-radius: 6px; border: 2px solid #FFE000;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<i class="fas fa-file-pdf" style="color: #E74C3C; font-size: 24px;"></i>
|
||
<div>
|
||
<div style="font-weight: 600; color: #111111;" id="power-confirmation-name">Bestätigung.pdf</div>
|
||
<div style="font-size: 12px; color: #666;" id="power-confirmation-size">0 KB</div>
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<button class="btn btn-secondary btn-small" onclick="downloadPowerConfirmation()" title="Herunterladen">
|
||
<i class="fas fa-download"></i>
|
||
</button>
|
||
<button class="btn btn-secondary btn-small" onclick="removePowerConfirmation()" title="Entfernen">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3 class="section-title">WLAN</h3>
|
||
|
||
<div class="form-group">
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="edit-wlan" onchange="toggleWlanFields()">
|
||
<label for="edit-wlan">WLAN gebucht</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="wlan-fields" style="display: none; margin-top: 16px;">
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>Art</label>
|
||
<input type="text" id="edit-wlan-type" placeholder="z.B. Standard, Premium, Business">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>🗓️ Bestelldeadline</label>
|
||
<input type="date" id="edit-wlan-deadline">
|
||
</div>
|
||
|
||
<div class="form-group" style="grid-column: 1 / -1;">
|
||
<label>Buchungsbestätigung (PDF)</label>
|
||
<input type="file" id="edit-wlan-confirmation-upload" accept=".pdf" onchange="handleWlanConfirmationUpload(event)" style="display: none;">
|
||
<button class="btn btn-secondary btn-small" onclick="document.getElementById('edit-wlan-confirmation-upload').click()">
|
||
<i class="fas fa-upload"></i> PDF hochladen
|
||
</button>
|
||
|
||
<div id="wlan-confirmation-preview" style="display: none; margin-top: 12px; padding: 12px; background: #F5F5F5; border-radius: 6px; border: 2px solid #FFE000;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<i class="fas fa-file-pdf" style="color: #E74C3C; font-size: 24px;"></i>
|
||
<div>
|
||
<div style="font-weight: 600; color: #111111;" id="wlan-confirmation-name">Bestätigung.pdf</div>
|
||
<div style="font-size: 12px; color: #666;" id="wlan-confirmation-size">0 KB</div>
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<button class="btn btn-secondary btn-small" onclick="downloadWlanConfirmation()" title="Herunterladen">
|
||
<i class="fas fa-download"></i>
|
||
</button>
|
||
<button class="btn btn-secondary btn-small" onclick="removeWlanConfirmation()" title="Entfernen">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3 class="section-title">Reinigungsservice</h3>
|
||
|
||
<div class="form-group">
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="edit-cleaning" onchange="toggleCleaningFields()">
|
||
<label for="edit-cleaning">Reinigungsservice gebucht</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="cleaning-fields" style="display: none; margin-top: 16px;">
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>🗓️ Bestelldeadline</label>
|
||
<input type="date" id="edit-cleaning-deadline">
|
||
</div>
|
||
|
||
<div class="form-group" style="grid-column: 1 / -1;">
|
||
<label>Buchungsbestätigung (PDF)</label>
|
||
<input type="file" id="edit-cleaning-confirmation-upload" accept=".pdf" onchange="handleCleaningConfirmationUpload(event)" style="display: none;">
|
||
<button class="btn btn-secondary btn-small" onclick="document.getElementById('edit-cleaning-confirmation-upload').click()">
|
||
<i class="fas fa-upload"></i> PDF hochladen
|
||
</button>
|
||
|
||
<div id="cleaning-confirmation-preview" style="display: none; margin-top: 12px; padding: 12px; background: #F5F5F5; border-radius: 6px; border: 2px solid #FFE000;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<i class="fas fa-file-pdf" style="color: #E74C3C; font-size: 24px;"></i>
|
||
<div>
|
||
<div style="font-weight: 600; color: #111111;" id="cleaning-confirmation-name">Bestätigung.pdf</div>
|
||
<div style="font-size: 12px; color: #666;" id="cleaning-confirmation-size">0 KB</div>
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<button class="btn btn-secondary btn-small" onclick="downloadCleaningConfirmation()" title="Herunterladen">
|
||
<i class="fas fa-download"></i>
|
||
</button>
|
||
<button class="btn btn-secondary btn-small" onclick="removeCleaningConfirmation()" title="Entfernen">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3 class="section-title">Rigging / Truss</h3>
|
||
|
||
<div class="form-group">
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="edit-rigging" onchange="toggleRiggingFields()">
|
||
<label for="edit-rigging">Rigging / Truss vorhanden</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="rigging-fields" style="display: none; margin-top: 16px;">
|
||
<h4 style="font-size: 14px; font-weight: 600; color: #111111; margin-bottom: 12px;">Lieferant Kontaktdaten</h4>
|
||
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>Vorname</label>
|
||
<input type="text" id="edit-rigging-firstname" placeholder="Max">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Nachname</label>
|
||
<input type="text" id="edit-rigging-lastname" placeholder="Mustermann">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>E-Mail</label>
|
||
<input type="email" id="edit-rigging-email" placeholder="max@rigging-firma.de">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Telefon</label>
|
||
<input type="tel" id="edit-rigging-phone" placeholder="+49 123 456789">
|
||
</div>
|
||
|
||
<div class="form-group" style="grid-column: 1 / -1;">
|
||
<label>🗓️ Bestelldeadline</label>
|
||
<input type="date" id="edit-rigging-deadline">
|
||
</div>
|
||
</div>
|
||
|
||
<h4 style="font-size: 14px; font-weight: 600; color: #111111; margin: 20px 0 12px 0;">Dokumente</h4>
|
||
|
||
<div class="form-grid">
|
||
<!-- Angebot Upload -->
|
||
<div class="form-group" style="grid-column: 1 / -1;">
|
||
<label>Angebot (PDF)</label>
|
||
<input type="file" id="edit-rigging-quote-upload" accept=".pdf" onchange="handleRiggingQuoteUpload(event)" style="display: none;">
|
||
<button class="btn btn-secondary btn-small" onclick="document.getElementById('edit-rigging-quote-upload').click()">
|
||
<i class="fas fa-upload"></i> Angebot hochladen
|
||
</button>
|
||
|
||
<div id="rigging-quote-preview" style="display: none; margin-top: 12px; padding: 12px; background: #F5F5F5; border-radius: 6px; border: 2px solid #FFE000;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<i class="fas fa-file-pdf" style="color: #E74C3C; font-size: 24px;"></i>
|
||
<div>
|
||
<div style="font-weight: 600; color: #111111;" id="rigging-quote-name">Angebot.pdf</div>
|
||
<div style="font-size: 12px; color: #666;" id="rigging-quote-size">0 KB</div>
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<button class="btn btn-secondary btn-small" onclick="downloadRiggingQuote()" title="Herunterladen">
|
||
<i class="fas fa-download"></i>
|
||
</button>
|
||
<button class="btn btn-secondary btn-small" onclick="removeRiggingQuote()" title="Entfernen">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Technische Zeichnung Upload -->
|
||
<div class="form-group" style="grid-column: 1 / -1;">
|
||
<label>Technische Zeichnung (PDF)</label>
|
||
<input type="file" id="edit-rigging-drawing-upload" accept=".pdf" onchange="handleRiggingDrawingUpload(event)" style="display: none;">
|
||
<button class="btn btn-secondary btn-small" onclick="document.getElementById('edit-rigging-drawing-upload').click()">
|
||
<i class="fas fa-upload"></i> Zeichnung hochladen
|
||
</button>
|
||
|
||
<div id="rigging-drawing-preview" style="display: none; margin-top: 12px; padding: 12px; background: #F5F5F5; border-radius: 6px; border: 2px solid #FFE000;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<i class="fas fa-file-pdf" style="color: #E74C3C; font-size: 24px;"></i>
|
||
<div>
|
||
<div style="font-weight: 600; color: #111111;" id="rigging-drawing-name">Zeichnung.pdf</div>
|
||
<div style="font-size: 12px; color: #666;" id="rigging-drawing-size">0 KB</div>
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<button class="btn btn-secondary btn-small" onclick="downloadRiggingDrawing()" title="Herunterladen">
|
||
<i class="fas fa-download"></i>
|
||
</button>
|
||
<button class="btn btn-secondary btn-small" onclick="removeRiggingDrawing()" title="Entfernen">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Auftragsbestätigung Messe Upload -->
|
||
<div class="form-group" style="grid-column: 1 / -1;">
|
||
<label>Auftragsbestätigung Messe (PDF)</label>
|
||
<input type="file" id="edit-rigging-messe-confirmation-upload" accept=".pdf" onchange="handleRiggingMesseConfirmationUpload(event)" style="display: none;">
|
||
<button class="btn btn-secondary btn-small" onclick="document.getElementById('edit-rigging-messe-confirmation-upload').click()">
|
||
<i class="fas fa-upload"></i> Auftragsbestätigung hochladen
|
||
</button>
|
||
|
||
<div id="rigging-messe-confirmation-preview" style="display: none; margin-top: 12px; padding: 12px; background: #F5F5F5; border-radius: 6px; border: 2px solid #FFE000;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<i class="fas fa-file-pdf" style="color: #E74C3C; font-size: 24px;"></i>
|
||
<div>
|
||
<div style="font-weight: 600; color: #111111;" id="rigging-messe-confirmation-name">Auftragsbestätigung.pdf</div>
|
||
<div style="font-size: 12px; color: #666;" id="rigging-messe-confirmation-size">0 KB</div>
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<button class="btn btn-secondary btn-small" onclick="downloadRiggingMesseConfirmation()" title="Herunterladen">
|
||
<i class="fas fa-download"></i>
|
||
</button>
|
||
<button class="btn btn-secondary btn-small" onclick="removeRiggingMesseConfirmation()" title="Entfernen">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h4 style="font-size: 14px; font-weight: 600; color: #111111; margin: 20px 0 12px 0;">Messe Kontaktdaten</h4>
|
||
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>Vorname</label>
|
||
<input type="text" id="edit-rigging-messe-firstname" placeholder="Max">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Nachname</label>
|
||
<input type="text" id="edit-rigging-messe-lastname" placeholder="Mustermann">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>E-Mail</label>
|
||
<input type="email" id="edit-rigging-messe-email" placeholder="max@messe.de">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Telefon</label>
|
||
<input type="tel" id="edit-rigging-messe-phone" placeholder="+49 123 456789">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3 class="section-title">Standfreigabe der Messe</h3>
|
||
|
||
<div class="form-group">
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="edit-stand-approval" onchange="toggleStandApprovalFields()">
|
||
<label for="edit-stand-approval">Standfreigabe vorhanden</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="stand-approval-fields" style="display: none; margin-top: 16px;">
|
||
<div class="form-group">
|
||
<label>🗓️ Bestelldeadline</label>
|
||
<input type="date" id="edit-stand-approval-deadline">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Freigabedokument (PDF)</label>
|
||
<input type="file" id="edit-stand-approval-upload" accept=".pdf" onchange="handleStandApprovalUpload(event)" style="display: none;">
|
||
<button class="btn btn-secondary btn-small" onclick="document.getElementById('edit-stand-approval-upload').click()">
|
||
<i class="fas fa-upload"></i> PDF hochladen
|
||
</button>
|
||
|
||
<div id="stand-approval-preview" style="display: none; margin-top: 12px; padding: 12px; background: #F5F5F5; border-radius: 6px; border: 2px solid #FFE000;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<i class="fas fa-file-pdf" style="color: #E74C3C; font-size: 24px;"></i>
|
||
<div>
|
||
<div style="font-weight: 600; color: #111111;" id="stand-approval-name">Freigabe.pdf</div>
|
||
<div style="font-size: 12px; color: #666;" id="stand-approval-size">0 KB</div>
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<button class="btn btn-secondary btn-small" onclick="downloadStandApproval()" title="Herunterladen">
|
||
<i class="fas fa-download"></i>
|
||
</button>
|
||
<button class="btn btn-secondary btn-small" onclick="removeStandApproval()" title="Entfernen">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3 class="section-title">Logistik</h3>
|
||
|
||
<div id="vehicles-container"></div>
|
||
|
||
<button class="btn btn-secondary btn-small" id="add-vehicle-btn">
|
||
<i class="fas fa-plus"></i> Fahrzeug hinzufügen
|
||
</button>
|
||
|
||
<h3 class="section-title">Hotelbuchungen</h3>
|
||
|
||
<div id="hotels-container"></div>
|
||
|
||
<button class="btn btn-secondary btn-small" id="add-hotel-btn">
|
||
<i class="fas fa-plus"></i> Hotel hinzufügen
|
||
</button>
|
||
|
||
<h3 class="section-title">
|
||
Personalplanung
|
||
<span style="font-size: 0.9rem; font-weight: 600; color: var(--text-secondary);" id="team-count">(0 Personen)</span>
|
||
</h3>
|
||
|
||
<div id="team-container"></div>
|
||
|
||
<button class="btn btn-secondary btn-small" id="add-team-btn">
|
||
<i class="fas fa-plus"></i> Mitarbeiter hinzufügen
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Tab 4: Produktion -->
|
||
<div class="tab-pane" id="tab-3">
|
||
<h3 class="section-title">Materialien</h3>
|
||
|
||
<div class="list-header">
|
||
<strong>Bodenbeläge</strong>
|
||
<button class="btn btn-secondary btn-small" onclick="addMaterial('flooring')">
|
||
<i class="fas fa-plus"></i> Hinzufügen
|
||
</button>
|
||
</div>
|
||
<div id="flooring-container"></div>
|
||
|
||
<div class="list-header" style="margin-top: 24px;">
|
||
<strong>Standsystem / Messebau-Elemente</strong>
|
||
<button class="btn btn-secondary btn-small" onclick="addMaterial('standsystem')">
|
||
<i class="fas fa-plus"></i> Hinzufügen
|
||
</button>
|
||
</div>
|
||
<div id="standsystem-container"></div>
|
||
|
||
<!-- Druckproduktion -->
|
||
<h3 class="section-title" style="margin-top: 32px;">Druckproduktion</h3>
|
||
|
||
<div class="form-group">
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="druckproduktion-enabled" onchange="toggleDruckproduktion()">
|
||
<label for="druckproduktion-enabled">Druckproduktion vorhanden</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="druckproduktion-fields" style="display: none; margin-top: 16px; padding: 16px; background: #F5F5F5; border-radius: 6px;">
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>Quadratmeter (m²)</label>
|
||
<input type="number" id="druckproduktion-qm" placeholder="0" min="0" step="0.01">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Blockout</label>
|
||
<input type="text" id="druckproduktion-blockout" placeholder="z.B. 270g/m²">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Anzahl Forexplatten</label>
|
||
<input type="number" id="druckproduktion-forexplatten" placeholder="0" min="0" step="1">
|
||
</div>
|
||
|
||
<div class="form-group" style="grid-column: 1 / -1;">
|
||
<label>🗓️ Bestelldeadline</label>
|
||
<input type="date" id="druckproduktion-deadline">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="list-header" style="margin-top: 24px;">
|
||
<strong>Hardware / Technik</strong>
|
||
<button class="btn btn-secondary btn-small" onclick="addMaterial('hardware')">
|
||
<i class="fas fa-plus"></i> Hinzufügen
|
||
</button>
|
||
</div>
|
||
<div id="hardware-container"></div>
|
||
|
||
<div class="list-header" style="margin-top: 24px;">
|
||
<strong>Möbel & Ausstattung</strong>
|
||
<button class="btn btn-secondary btn-small" onclick="addMaterial('furniture')">
|
||
<i class="fas fa-plus"></i> Hinzufügen
|
||
</button>
|
||
</div>
|
||
<div id="furniture-container"></div>
|
||
|
||
<!-- Catering -->
|
||
<h3 class="section-title" style="margin-top: 32px;">Catering</h3>
|
||
|
||
<div class="form-group">
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="catering-barista" onchange="toggleBaristaFields()">
|
||
<label for="catering-barista">Barista-Service</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="barista-fields" style="display: none; margin-top: 16px; padding: 16px; background: #F5F5F5; border-radius: 6px;">
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>Service-Paket</label>
|
||
<select id="catering-barista-package">
|
||
<option value="">-- Bitte wählen --</option>
|
||
<option value="1-pax-150">1 PAX - 150 Kaffee/pro Tag</option>
|
||
<option value="1-pax-300">1 PAX - 300 Kaffee/pro Tag</option>
|
||
<option value="2-pax-150">2 PAX - 150 Kaffee/pro Tag</option>
|
||
<option value="2-pax-300">2 PAX - 300 Kaffee/pro Tag</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Anzahl Messetage</label>
|
||
<input type="number" id="catering-days" min="1" placeholder="z.B. 3">
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top: 16px;">
|
||
<div class="list-header">
|
||
<strong>Catering-Personal</strong>
|
||
<button class="btn btn-secondary btn-small" onclick="addCateringStaff()">
|
||
<i class="fas fa-plus"></i> Person hinzufügen
|
||
</button>
|
||
</div>
|
||
<div id="catering-staff-container" style="margin-top: 12px;"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Getränke -->
|
||
<div class="form-group" style="margin-top: 24px;">
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="catering-drinks" onchange="toggleDrinksFields()">
|
||
<label for="catering-drinks">Getränke</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="drinks-fields" style="display: none; margin-top: 16px; padding: 16px; background: #F5F5F5; border-radius: 6px;">
|
||
<div class="form-group">
|
||
<label>Getränke-Bestellungen</label>
|
||
<textarea id="catering-drinks-details" rows="4" placeholder="z.B. 100 Flaschen Wasser, 50 Flaschen Cola, 30 Flaschen Saft..." style="width: 100%; padding: 12px; border: 1px solid #E0E0E0; border-radius: 6px; font-size: 14px; font-family: Inter, sans-serif; resize: vertical;"></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Food -->
|
||
<div class="form-group" style="margin-top: 24px;">
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="catering-food" onchange="toggleFoodFields()">
|
||
<label for="catering-food">Food</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="food-fields" style="display: none; margin-top: 16px; padding: 16px; background: #F5F5F5; border-radius: 6px;">
|
||
<div class="form-group">
|
||
<label>Food-Bestellungen</label>
|
||
<textarea id="catering-food-details" rows="4" placeholder="z.B. 50 Belegte Brötchen, 30 Wraps, Obstplatten..." style="width: 100%; padding: 12px; border: 1px solid #E0E0E0; border-radius: 6px; font-size: 14px; font-family: Inter, sans-serif; resize: vertical;"></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<h3 class="section-title">Leadmagneten</h3>
|
||
|
||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="lead-flyer">
|
||
<label for="lead-flyer">Flyer / Broschüren</label>
|
||
</div>
|
||
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="lead-giveaway">
|
||
<label for="lead-giveaway">Giveaways / Geschenke</label>
|
||
</div>
|
||
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="lead-tablet">
|
||
<label for="lead-tablet">Tablet für Datenerfassung</label>
|
||
</div>
|
||
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="lead-scanner">
|
||
<label for="lead-scanner">Badge-Scanner</label>
|
||
</div>
|
||
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="lead-contest">
|
||
<label for="lead-contest">Gewinnspiel</label>
|
||
</div>
|
||
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="lead-demo">
|
||
<label for="lead-demo">Produkt-Demos</label>
|
||
</div>
|
||
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="lead-photo-booth">
|
||
<label for="lead-photo-booth">KI Fotobox</label>
|
||
</div>
|
||
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="lead-vending-machine">
|
||
<label for="lead-vending-machine">Vending Machine</label>
|
||
</div>
|
||
|
||
<div class="checklist-item">
|
||
<input type="checkbox" id="lead-grab-arm">
|
||
<label for="lead-grab-arm">Greif-Arm</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab 5: Dokumente -->
|
||
<div class="tab-pane" id="tab-4">
|
||
<h3 class="section-title">Checkliste</h3>
|
||
|
||
<div id="checklist-container"></div>
|
||
|
||
<button class="btn btn-secondary btn-small" style="margin-top: 16px;" id="add-task-btn">
|
||
<i class="fas fa-plus"></i> Eigene Aufgabe hinzufügen
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Tab 6: Standfotos -->
|
||
<div class="tab-pane" id="tab-5">
|
||
<h3 class="section-title">Standfotos</h3>
|
||
|
||
<div class="gallery-upload">
|
||
<input type="file" id="gallery-upload-input" accept="image/*" multiple>
|
||
<label for="gallery-upload-input" class="gallery-upload-label">
|
||
<i class="fas fa-cloud-upload-alt"></i> Bilder hochladen
|
||
</label>
|
||
<div class="gallery-upload-info">
|
||
<i class="fas fa-info-circle"></i> Max. 10 Bilder, je max. 2 MB (JPG, PNG, GIF)
|
||
</div>
|
||
</div>
|
||
|
||
<div id="gallery-container">
|
||
<div class="gallery-empty">
|
||
<i class="fas fa-images"></i>
|
||
<p>Noch keine Standfotos hochgeladen</p>
|
||
<p style="font-size: 0.85rem; margin-top: 8px;">Laden Sie Bilder Ihres Messestandes hoch, um eine Galerie zu erstellen.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab 7: Standabnahme -->
|
||
<div class="tab-pane" id="tab-6">
|
||
<h3 class="section-title">Standabnahme</h3>
|
||
|
||
<p style="color: #666; margin-bottom: 24px;">
|
||
Dokumentieren Sie die Abnahme des fertigen Standes mit Fotos, Unterschrift und Bemerkungen.
|
||
</p>
|
||
|
||
<!-- Abnahmefotos Upload -->
|
||
<h4 style="font-size: 16px; font-weight: 600; color: #111111; margin-bottom: 12px;">
|
||
<i class="fas fa-camera"></i> Abnahmefotos
|
||
</h4>
|
||
|
||
<div class="gallery-upload" style="margin-bottom: 24px;">
|
||
<input type="file" id="acceptance-upload-input" accept="image/*" multiple>
|
||
<label for="acceptance-upload-input" class="gallery-upload-label">
|
||
<i class="fas fa-cloud-upload-alt"></i> Fotos hochladen
|
||
</label>
|
||
<div class="gallery-upload-info">
|
||
<i class="fas fa-info-circle"></i> Max. 10 Bilder, je max. 2 MB (JPG, PNG, GIF)
|
||
</div>
|
||
</div>
|
||
|
||
<div id="acceptance-gallery-container">
|
||
<div class="gallery-empty">
|
||
<i class="fas fa-camera"></i>
|
||
<p>Noch keine Abnahmefotos hochgeladen</p>
|
||
<p style="font-size: 0.85rem; margin-top: 8px;">Fotografieren Sie den fertigen Stand für die Abnahme.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Unterschrift -->
|
||
<h4 style="font-size: 16px; font-weight: 600; color: #111111; margin: 32px 0 12px 0;">
|
||
<i class="fas fa-signature"></i> Unterschrift Kunde
|
||
</h4>
|
||
|
||
<div style="background: #F5F5F5; padding: 16px; border-radius: 8px; margin-bottom: 16px;">
|
||
<canvas id="signature-canvas" width="600" height="200" style="width: 100%; max-width: 600px; height: auto; border: 2px solid #E0E0E0; background: white; border-radius: 6px; cursor: crosshair; display: block; touch-action: none;"></canvas>
|
||
<div style="display: flex; gap: 8px; margin-top: 12px;">
|
||
<button class="btn btn-secondary btn-small" onclick="clearSignature()">
|
||
<i class="fas fa-eraser"></i> Löschen
|
||
</button>
|
||
<button class="btn btn-secondary btn-small" onclick="saveSignature()">
|
||
<i class="fas fa-save"></i> Unterschrift speichern
|
||
</button>
|
||
</div>
|
||
<div id="signature-saved-indicator" style="display: none; margin-top: 12px; padding: 8px; background: #E8F5E9; border-radius: 4px; color: #2E7D32;">
|
||
<i class="fas fa-check-circle"></i> Unterschrift gespeichert
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bemerkungen -->
|
||
<h4 style="font-size: 16px; font-weight: 600; color: #111111; margin-bottom: 12px;">
|
||
<i class="fas fa-comment-alt"></i> Bemerkungen zur Abnahme
|
||
</h4>
|
||
|
||
<textarea id="acceptance-remarks" rows="6" placeholder="Notizen, Mängel, Besonderheiten..." style="width: 100%; padding: 12px; border: 1px solid #E0E0E0; border-radius: 6px; font-size: 14px; font-family: Inter, sans-serif; resize: vertical;"></textarea>
|
||
|
||
<!-- Abnahmedatum -->
|
||
<div style="margin-top: 24px;">
|
||
<label style="display: block; font-weight: 600; margin-bottom: 8px;">Abnahmedatum</label>
|
||
<input type="datetime-local" id="acceptance-date" style="padding: 8px; border: 1px solid #E0E0E0; border-radius: 4px; font-size: 14px;">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab 8: Weekly -->
|
||
<div class="tab-pane" id="tab-7">
|
||
<h3 class="section-title">Weekly Protokolle</h3>
|
||
|
||
<p style="color: #666; margin-bottom: 24px;">
|
||
Laden Sie wöchentliche Protokolle und Berichte hoch. Mehrere Dateien können gleichzeitig hochgeladen werden.
|
||
</p>
|
||
|
||
<!-- Protokoll Upload -->
|
||
<div class="form-group" style="margin-bottom: 24px;">
|
||
<label style="display: block; font-weight: 600; margin-bottom: 12px;">
|
||
<i class="fas fa-file-upload"></i> Protokolle hochladen (PDF)
|
||
</label>
|
||
<input type="file" id="weekly-protocol-upload" accept=".pdf" multiple style="display: none;">
|
||
<button class="btn btn-secondary" onclick="document.getElementById('weekly-protocol-upload').click()">
|
||
<i class="fas fa-cloud-upload-alt"></i> Protokolle hochladen
|
||
</button>
|
||
<div style="margin-top: 8px; font-size: 13px; color: #666;">
|
||
<i class="fas fa-info-circle"></i> Mehrere PDF-Dateien auswählbar, je max. 10 MB
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Protokoll Liste -->
|
||
<h4 style="font-size: 16px; font-weight: 600; color: #111111; margin-bottom: 16px;">
|
||
<i class="fas fa-list"></i> Hochgeladene Protokolle
|
||
</h4>
|
||
|
||
<div id="weekly-protocols-container">
|
||
<div class="gallery-empty">
|
||
<i class="fas fa-file-pdf"></i>
|
||
<p>Noch keine Protokolle hochgeladen</p>
|
||
<p style="font-size: 0.85rem; margin-top: 8px;">Laden Sie wöchentliche Berichte und Protokolle hoch.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<!-- Toast -->
|
||
<div id="toast" class="toast">
|
||
<i class="fas fa-check-circle"></i>
|
||
<span id="toast-message">Gespeichert!</span>
|
||
</div>
|
||
|
||
<!-- Lightbox -->
|
||
<div id="lightbox" class="lightbox">
|
||
<div class="lightbox-content">
|
||
<button class="lightbox-close" id="lightbox-close">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
<button class="lightbox-nav prev" id="lightbox-prev">
|
||
<i class="fas fa-chevron-left"></i>
|
||
</button>
|
||
<button class="lightbox-nav next" id="lightbox-next">
|
||
<i class="fas fa-chevron-right"></i>
|
||
</button>
|
||
<img id="lightbox-image" class="lightbox-image" src="" alt="Standfoto">
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Test ob JavaScript läuft
|
||
console.log('🚀 24HRS Plattform startet...');
|
||
|
||
// Global Variables (use var to attach to window automatically)
|
||
var jobs = [];
|
||
var currentJobId = null;
|
||
|
||
// Initialize
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
console.log('✅ DOM geladen');
|
||
|
||
// Load jobs from localStorage
|
||
loadJobs();
|
||
console.log('✅ Jobs geladen:', jobs.length);
|
||
|
||
// Render dashboard
|
||
updateStats();
|
||
renderJobs();
|
||
console.log('✅ Dashboard gerendert');
|
||
|
||
// Back button
|
||
const backBtn = document.getElementById('back-btn');
|
||
if (backBtn) {
|
||
backBtn.addEventListener('click', showDashboard);
|
||
}
|
||
|
||
// Save button
|
||
const saveBtn = document.getElementById('save-job-btn');
|
||
if (saveBtn) {
|
||
saveBtn.addEventListener('click', saveCurrentJob);
|
||
}
|
||
|
||
// Duplicate button
|
||
const duplicateBtn = document.getElementById('duplicate-job-btn');
|
||
if (duplicateBtn) {
|
||
duplicateBtn.addEventListener('click', duplicateCurrentJob);
|
||
}
|
||
|
||
// Export button
|
||
const exportBtn = document.getElementById('export-job-btn');
|
||
if (exportBtn) {
|
||
exportBtn.addEventListener('click', exportJobToPDF);
|
||
}
|
||
|
||
// Delete button
|
||
const deleteBtn = document.getElementById('delete-job-btn');
|
||
if (deleteBtn) {
|
||
deleteBtn.addEventListener('click', deleteCurrentJob);
|
||
}
|
||
|
||
// New Job buttons - SIMPLIFIED VERSION
|
||
const newJobBtn = document.getElementById('new-job-btn');
|
||
if (newJobBtn) {
|
||
newJobBtn.onclick = function() {
|
||
console.log('🖱️ Neuer Job Button geklickt!');
|
||
if (typeof window.createNewJob === 'function') {
|
||
window.createNewJob();
|
||
} else {
|
||
console.error('❌ createNewJob Funktion nicht gefunden!');
|
||
}
|
||
};
|
||
console.log('✅ Button Event Listener registriert');
|
||
} else {
|
||
console.error('❌ Button nicht gefunden!');
|
||
}
|
||
|
||
const emptyNewJobBtn = document.getElementById('empty-new-job-btn');
|
||
if (emptyNewJobBtn) {
|
||
emptyNewJobBtn.onclick = function() {
|
||
console.log('🖱️ Empty Neuer Job Button geklickt!');
|
||
if (typeof window.createNewJob === 'function') {
|
||
window.createNewJob();
|
||
} else {
|
||
console.error('❌ createNewJob Funktion nicht gefunden!');
|
||
}
|
||
};
|
||
}
|
||
|
||
// Tab buttons
|
||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
const tabIndex = this.getAttribute('data-tab');
|
||
switchTab(tabIndex);
|
||
});
|
||
});
|
||
|
||
// Add hotel button
|
||
const addHotelBtn = document.getElementById('add-hotel-btn');
|
||
if (addHotelBtn) {
|
||
addHotelBtn.addEventListener('click', addHotel);
|
||
}
|
||
|
||
// Add vehicle button
|
||
const addVehicleBtn = document.getElementById('add-vehicle-btn');
|
||
if (addVehicleBtn) {
|
||
addVehicleBtn.addEventListener('click', addVehicle);
|
||
}
|
||
|
||
// Add team button
|
||
const addTeamBtn = document.getElementById('add-team-btn');
|
||
if (addTeamBtn) {
|
||
addTeamBtn.addEventListener('click', addTeamMember);
|
||
}
|
||
|
||
// Add task button
|
||
const addTaskBtn = document.getElementById('add-task-btn');
|
||
if (addTaskBtn) {
|
||
addTaskBtn.addEventListener('click', addChecklistItem);
|
||
}
|
||
|
||
// Stand image upload
|
||
const standImageUpload = document.getElementById('stand-image-upload');
|
||
if (standImageUpload) {
|
||
standImageUpload.addEventListener('change', handleStandImageUpload);
|
||
}
|
||
|
||
// Download stand image button
|
||
const downloadStandImageBtn = document.getElementById('download-stand-image-btn');
|
||
if (downloadStandImageBtn) {
|
||
downloadStandImageBtn.addEventListener('click', downloadStandImage);
|
||
}
|
||
|
||
// Remove stand image button
|
||
const removeStandImageBtn = document.getElementById('remove-stand-image-btn');
|
||
if (removeStandImageBtn) {
|
||
removeStandImageBtn.addEventListener('click', removeStandImage);
|
||
}
|
||
|
||
// Gallery upload
|
||
const galleryUploadInput = document.getElementById('gallery-upload-input');
|
||
if (galleryUploadInput) {
|
||
galleryUploadInput.addEventListener('change', handleGalleryUpload);
|
||
}
|
||
|
||
// Acceptance upload
|
||
const acceptanceUploadInput = document.getElementById('acceptance-upload-input');
|
||
if (acceptanceUploadInput) {
|
||
acceptanceUploadInput.addEventListener('change', handleAcceptancePhotosUpload);
|
||
}
|
||
|
||
// Weekly protocol upload
|
||
const weeklyProtocolUpload = document.getElementById('weekly-protocol-upload');
|
||
if (weeklyProtocolUpload) {
|
||
weeklyProtocolUpload.addEventListener('change', handleWeeklyProtocolUpload);
|
||
}
|
||
|
||
// Lightbox close
|
||
const lightboxClose = document.getElementById('lightbox-close');
|
||
if (lightboxClose) {
|
||
lightboxClose.addEventListener('click', closeLightbox);
|
||
}
|
||
|
||
// Lightbox navigation
|
||
const lightboxPrev = document.getElementById('lightbox-prev');
|
||
const lightboxNext = document.getElementById('lightbox-next');
|
||
if (lightboxPrev) {
|
||
lightboxPrev.addEventListener('click', () => navigateLightbox(-1));
|
||
}
|
||
if (lightboxNext) {
|
||
lightboxNext.addEventListener('click', () => navigateLightbox(1));
|
||
}
|
||
|
||
// Lightbox background click to close
|
||
const lightbox = document.getElementById('lightbox');
|
||
if (lightbox) {
|
||
lightbox.addEventListener('click', function(e) {
|
||
if (e.target === lightbox) {
|
||
closeLightbox();
|
||
}
|
||
});
|
||
}
|
||
|
||
// Navigation items
|
||
const navDashboard = document.getElementById('nav-dashboard');
|
||
const navCalendar = document.getElementById('nav-calendar');
|
||
|
||
if (navDashboard) {
|
||
navDashboard.addEventListener('click', showDashboardView);
|
||
}
|
||
|
||
if (navCalendar) {
|
||
navCalendar.addEventListener('click', showCalendarView);
|
||
}
|
||
|
||
// Calendar navigation
|
||
const calendarPrevBtn = document.getElementById('calendar-prev-month');
|
||
const calendarNextBtn = document.getElementById('calendar-next-month');
|
||
const calendarTodayBtn = document.getElementById('calendar-today');
|
||
|
||
if (calendarPrevBtn) {
|
||
calendarPrevBtn.addEventListener('click', () => changeMonth(-1));
|
||
}
|
||
|
||
if (calendarNextBtn) {
|
||
calendarNextBtn.addEventListener('click', () => changeMonth(1));
|
||
}
|
||
|
||
if (calendarTodayBtn) {
|
||
calendarTodayBtn.addEventListener('click', goToToday);
|
||
}
|
||
|
||
// Briefing upload event listeners
|
||
const briefingCustomerUpload = document.getElementById('briefing-customer-upload');
|
||
if (briefingCustomerUpload) {
|
||
briefingCustomerUpload.addEventListener('change', handleBriefingCustomerUpload);
|
||
}
|
||
|
||
const briefingBrandUpload = document.getElementById('briefing-brand-upload');
|
||
if (briefingBrandUpload) {
|
||
briefingBrandUpload.addEventListener('change', handleBriefingBrandUpload);
|
||
}
|
||
|
||
const briefingPlanUpload = document.getElementById('briefing-plan-upload');
|
||
if (briefingPlanUpload) {
|
||
briefingPlanUpload.addEventListener('change', handleBriefingPlanUpload);
|
||
}
|
||
|
||
const briefingAdditionalUpload = document.getElementById('briefing-additional-upload');
|
||
if (briefingAdditionalUpload) {
|
||
briefingAdditionalUpload.addEventListener('change', handleBriefingAdditionalUpload);
|
||
}
|
||
|
||
// Initialize signature pad for acceptance tab
|
||
initSignaturePad();
|
||
|
||
showToast('Plattform bereit! ✓');
|
||
});
|
||
|
||
// Load jobs from localStorage
|
||
function loadJobs() {
|
||
try {
|
||
const saved = localStorage.getItem('messebau_jobs');
|
||
if (saved) {
|
||
const parsed = JSON.parse(saved);
|
||
// Validate and fix loaded jobs
|
||
if (Array.isArray(parsed)) {
|
||
jobs = parsed.filter(job => {
|
||
// Ensure job has minimum required fields
|
||
if (!job || typeof job !== 'object') return false;
|
||
if (!job.id) {
|
||
console.warn('Job ohne ID gefunden, wird übersprungen');
|
||
return false;
|
||
}
|
||
return true;
|
||
});
|
||
console.log('Geladene Jobs:', jobs.length);
|
||
} else {
|
||
console.warn('Ungültiges Jobs-Array, wird zurückgesetzt');
|
||
jobs = [];
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error('Fehler beim Laden:', e);
|
||
console.warn('LocalStorage wird zurückgesetzt');
|
||
jobs = [];
|
||
localStorage.removeItem('messebau_jobs');
|
||
}
|
||
}
|
||
|
||
// Save jobs to localStorage
|
||
function saveJobs() {
|
||
try {
|
||
localStorage.setItem('messebau_jobs', JSON.stringify(jobs));
|
||
console.log('Jobs gespeichert:', jobs.length);
|
||
} catch (e) {
|
||
console.error('Fehler beim Speichern:', e);
|
||
}
|
||
}
|
||
|
||
// Update statistics
|
||
function updateStats() {
|
||
const total = jobs.length;
|
||
const open = jobs.filter(j => j.status !== 'Abgeschlossen').length;
|
||
const thisWeek = 0; // Simplified for now
|
||
|
||
document.getElementById('stat-total').textContent = total;
|
||
document.getElementById('stat-open').textContent = open;
|
||
document.getElementById('stat-week').textContent = thisWeek;
|
||
}
|
||
|
||
// Render jobs
|
||
function renderJobs() {
|
||
const container = document.getElementById('jobs-container');
|
||
const emptyState = document.getElementById('empty-state');
|
||
|
||
if (!container || !emptyState) {
|
||
console.error('Jobs container oder empty state nicht gefunden!');
|
||
return;
|
||
}
|
||
|
||
if (jobs.length === 0) {
|
||
container.classList.add('hidden');
|
||
emptyState.classList.remove('hidden');
|
||
return;
|
||
}
|
||
|
||
container.classList.remove('hidden');
|
||
emptyState.classList.add('hidden');
|
||
|
||
container.innerHTML = jobs.map(job => `
|
||
<div class="job-card" onclick="openJob('${job.id}')">
|
||
${job.standImage ? `
|
||
<div class="job-card-image" style="height: 180px; overflow: hidden; background: var(--bg-light); display: flex; align-items: center; justify-content: center; border-bottom: 3px solid var(--primary-yellow);">
|
||
<img src="${job.standImage}" style="width: 100%; height: 100%; object-fit: cover;" alt="Messestand">
|
||
</div>
|
||
` : ''}
|
||
<div class="job-card-header">
|
||
<div class="job-id">Job #${job.id ? job.id.substring(0, 8) : 'unknown'}</div>
|
||
${job.project ? `<div class="job-project" style="font-size: 0.75rem; font-weight: 600; color: var(--primary-yellow); text-transform: uppercase; margin-bottom: 4px;"><i class="fas fa-folder"></i> ${job.project}</div>` : ''}
|
||
<div class="job-title">${job.customerName || 'Neuer Job'}</div>
|
||
<div class="job-subtitle">${job.eventName || 'Keine Messe'}</div>
|
||
</div>
|
||
<div class="job-card-body">
|
||
<div class="meta-item">
|
||
<i class="fas fa-map-marker-alt"></i>
|
||
<span>${job.location || 'Kein Ort'}</span>
|
||
</div>
|
||
<div class="meta-item">
|
||
<i class="fas fa-calendar"></i>
|
||
<span>${job.eventStart || 'Kein Datum'}</span>
|
||
</div>
|
||
<div class="progress-bar-container">
|
||
<div class="progress-bar" style="width: 0%"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
// Default checklist items
|
||
const defaultChecklist = [
|
||
'Standaufbau-Genehmigung einholen',
|
||
'Technische Anschlüsse bestellen',
|
||
'Ausstellerausweise bestellen',
|
||
'Hotelbuchungen bestätigen',
|
||
'Team-Einweisung durchführen',
|
||
'Material & Werkzeug einpacken',
|
||
'Grafiken zur Produktion freigeben',
|
||
'Transport & Logistik koordinieren'
|
||
];
|
||
|
||
// Create new job (GLOBAL FUNCTION)
|
||
window.createNewJob = function() {
|
||
console.log('🆕 Erstelle neuen Job...');
|
||
|
||
try {
|
||
const newJob = {
|
||
id: Date.now().toString(36) + Math.random().toString(36).substring(2),
|
||
customerName: 'Neuer Kunde',
|
||
eventName: 'Neue Messe',
|
||
location: '',
|
||
project: '',
|
||
standType: '',
|
||
standSize: '',
|
||
standHeight: '',
|
||
status: 'Offen',
|
||
eventStart: '',
|
||
eventEnd: '',
|
||
setupStart: '',
|
||
setupEnd: '',
|
||
teardownStart: '',
|
||
teardownEnd: '',
|
||
standImage: null,
|
||
standImageName: null,
|
||
standGallery: [],
|
||
bookingPlatform: {
|
||
enabled: false,
|
||
portalLink: '',
|
||
email: '',
|
||
password: '',
|
||
deadline: ''
|
||
},
|
||
powerBooking: {
|
||
enabled: false,
|
||
kw: '',
|
||
distributor: '',
|
||
confirmationPdf: null,
|
||
confirmationPdfName: null,
|
||
confirmationPdfSize: null,
|
||
deadline: ''
|
||
},
|
||
rigging: {
|
||
enabled: false,
|
||
supplierFirstname: '',
|
||
supplierLastname: '',
|
||
supplierEmail: '',
|
||
supplierPhone: '',
|
||
quotePdf: null,
|
||
quotePdfName: null,
|
||
quotePdfSize: null,
|
||
drawingPdf: null,
|
||
drawingPdfName: null,
|
||
drawingPdfSize: null,
|
||
messeConfirmationPdf: null,
|
||
messeConfirmationPdfName: null,
|
||
messeConfirmationPdfSize: null,
|
||
messeFirstname: '',
|
||
messeLastname: '',
|
||
messeEmail: '',
|
||
messePhone: '',
|
||
deadline: ''
|
||
},
|
||
wlan: {
|
||
enabled: false,
|
||
type: '',
|
||
confirmationPdf: null,
|
||
confirmationPdfName: null,
|
||
confirmationPdfSize: null,
|
||
deadline: ''
|
||
},
|
||
cleaning: {
|
||
enabled: false,
|
||
confirmationPdf: null,
|
||
confirmationPdfName: null,
|
||
confirmationPdfSize: null,
|
||
deadline: ''
|
||
},
|
||
standApproval: {
|
||
enabled: false,
|
||
pdf: null,
|
||
pdfName: null,
|
||
pdfSize: null,
|
||
deadline: ''
|
||
},
|
||
acceptance: {
|
||
photos: [],
|
||
signature: null,
|
||
remarks: '',
|
||
date: ''
|
||
},
|
||
weekly: {
|
||
protocols: []
|
||
},
|
||
briefing: {
|
||
customerDoc: null,
|
||
customerDocName: null,
|
||
customerDocSize: null,
|
||
brandDoc: null,
|
||
brandDocName: null,
|
||
brandDocSize: null,
|
||
planDoc: null,
|
||
planDocName: null,
|
||
planDocSize: null,
|
||
additionalDoc: null,
|
||
additionalDocName: null,
|
||
additionalDocSize: null
|
||
},
|
||
catering: {
|
||
baristaEnabled: false,
|
||
baristaPackage: '',
|
||
days: '',
|
||
staff: [],
|
||
drinksEnabled: false,
|
||
drinksDetails: '',
|
||
foodEnabled: false,
|
||
foodDetails: ''
|
||
},
|
||
druckproduktion: {
|
||
enabled: false,
|
||
qm: '',
|
||
blockout: '',
|
||
forexplatten: '',
|
||
deadline: ''
|
||
},
|
||
createdAt: new Date().toISOString(),
|
||
// Initialize other fields
|
||
vehicles: [],
|
||
hotels: [],
|
||
team: [],
|
||
materials: {
|
||
bodenbelag: [],
|
||
standsystem: [],
|
||
hardware: [],
|
||
moebel: []
|
||
},
|
||
leadMagnets: {
|
||
flyer: false,
|
||
giveaway: false,
|
||
tablet: false,
|
||
scanner: false,
|
||
gewinnspiel: false,
|
||
demo: false,
|
||
photobooth: false,
|
||
vendingMachine: false,
|
||
grabArm: false
|
||
},
|
||
checklist: defaultChecklist.map(item => ({ text: item, checked: false }))
|
||
};
|
||
|
||
jobs.push(newJob);
|
||
console.log('✅ Job erstellt:', newJob.id);
|
||
|
||
saveJobs();
|
||
updateStats();
|
||
renderJobs();
|
||
|
||
showToast('✅ Neuer Job erstellt!');
|
||
|
||
// Open the job immediately for editing
|
||
openJob(newJob.id);
|
||
|
||
} catch (error) {
|
||
console.error('❌ Fehler:', error);
|
||
alert('Fehler beim Erstellen des Jobs: ' + error.message);
|
||
}
|
||
}
|
||
|
||
// Open job for editing
|
||
window.openJob = function(jobId) {
|
||
console.log('📂 Öffne Job:', jobId);
|
||
|
||
currentJobId = jobId;
|
||
const job = jobs.find(j => j.id === jobId);
|
||
|
||
if (!job) {
|
||
console.error('Job nicht gefunden:', jobId);
|
||
return;
|
||
}
|
||
|
||
// Hide dashboard, show detail view
|
||
const dashboardView = document.getElementById('dashboard-view');
|
||
const calendarView = document.getElementById('calendar-view');
|
||
const jobDetailView = document.getElementById('job-detail-view');
|
||
|
||
if (dashboardView) dashboardView.classList.add('hidden');
|
||
if (calendarView) calendarView.classList.add('hidden');
|
||
if (jobDetailView) jobDetailView.classList.remove('hidden');
|
||
|
||
const navDashboard = document.getElementById('nav-dashboard');
|
||
const navCalendar = document.getElementById('nav-calendar');
|
||
|
||
if (navDashboard) navDashboard.classList.remove('active');
|
||
if (navCalendar) navCalendar.classList.remove('active');
|
||
|
||
// Fill form
|
||
document.getElementById('job-detail-title').textContent = job.customerName || 'Neuer Job';
|
||
document.getElementById('edit-customer').value = job.customerName || '';
|
||
document.getElementById('edit-event').value = job.eventName || '';
|
||
document.getElementById('edit-location').value = job.location || '';
|
||
document.getElementById('edit-project').value = job.project || '';
|
||
document.getElementById('edit-stand-type').value = job.standType || '';
|
||
document.getElementById('edit-stand-size').value = job.standSize || '';
|
||
document.getElementById('edit-stand-height').value = job.standHeight || '';
|
||
document.getElementById('edit-status').value = job.status || 'Offen';
|
||
document.getElementById('edit-event-start').value = job.eventStart || '';
|
||
|
||
// Load time schedule fields
|
||
document.getElementById('edit-setup-start').value = job.setupStart || '';
|
||
document.getElementById('edit-setup-end').value = job.setupEnd || '';
|
||
document.getElementById('edit-event-end').value = job.eventEnd || '';
|
||
document.getElementById('edit-teardown-start').value = job.teardownStart || '';
|
||
document.getElementById('edit-teardown-end').value = job.teardownEnd || '';
|
||
|
||
// Load stand image if exists
|
||
const preview = document.getElementById('stand-image-preview');
|
||
const previewImg = document.getElementById('stand-image-preview-img');
|
||
|
||
if (job.standImage) {
|
||
previewImg.src = job.standImage;
|
||
preview.style.display = 'block';
|
||
} else {
|
||
preview.style.display = 'none';
|
||
}
|
||
|
||
// Load gallery
|
||
renderGallery();
|
||
|
||
// Load booking platform data
|
||
const bookingPlatform = job.bookingPlatform || { enabled: false, portalLink: '', email: '', password: '' };
|
||
document.getElementById('edit-booking-platform').checked = bookingPlatform.enabled || false;
|
||
document.getElementById('edit-booking-portal-link').value = bookingPlatform.portalLink || '';
|
||
document.getElementById('edit-booking-email').value = bookingPlatform.email || '';
|
||
document.getElementById('edit-booking-password').value = bookingPlatform.password || '';
|
||
document.getElementById('edit-booking-deadline').value = bookingPlatform.deadline || '';
|
||
|
||
// Show/hide booking platform fields based on checkbox
|
||
toggleBookingPlatformFields();
|
||
|
||
// Load power booking data
|
||
const powerBooking = job.powerBooking || { enabled: false, kw: '', distributor: '', confirmationPdf: null };
|
||
document.getElementById('edit-power-booking').checked = powerBooking.enabled || false;
|
||
document.getElementById('edit-power-kw').value = powerBooking.kw || '';
|
||
document.getElementById('edit-power-distributor').value = powerBooking.distributor || '';
|
||
document.getElementById('edit-power-deadline').value = powerBooking.deadline || '';
|
||
|
||
// Show/hide power booking fields and PDF preview
|
||
togglePowerBookingFields();
|
||
|
||
if (powerBooking.confirmationPdf) {
|
||
document.getElementById('power-confirmation-preview').style.display = 'block';
|
||
document.getElementById('power-confirmation-name').textContent = powerBooking.confirmationPdfName || 'Bestätigung.pdf';
|
||
document.getElementById('power-confirmation-size').textContent = ((powerBooking.confirmationPdfSize || 0) / 1024).toFixed(1) + ' KB';
|
||
} else {
|
||
document.getElementById('power-confirmation-preview').style.display = 'none';
|
||
}
|
||
|
||
// Load rigging data
|
||
const rigging = job.rigging || { enabled: false, supplierFirstname: '', supplierLastname: '', supplierEmail: '', supplierPhone: '' };
|
||
document.getElementById('edit-rigging').checked = rigging.enabled || false;
|
||
document.getElementById('edit-rigging-firstname').value = rigging.supplierFirstname || '';
|
||
document.getElementById('edit-rigging-lastname').value = rigging.supplierLastname || '';
|
||
document.getElementById('edit-rigging-email').value = rigging.supplierEmail || '';
|
||
document.getElementById('edit-rigging-phone').value = rigging.supplierPhone || '';
|
||
document.getElementById('edit-rigging-deadline').value = rigging.deadline || '';
|
||
|
||
// Show/hide rigging fields and PDF previews
|
||
toggleRiggingFields();
|
||
|
||
if (rigging.quotePdf) {
|
||
document.getElementById('rigging-quote-preview').style.display = 'block';
|
||
document.getElementById('rigging-quote-name').textContent = rigging.quotePdfName || 'Angebot.pdf';
|
||
document.getElementById('rigging-quote-size').textContent = ((rigging.quotePdfSize || 0) / 1024).toFixed(1) + ' KB';
|
||
} else {
|
||
document.getElementById('rigging-quote-preview').style.display = 'none';
|
||
}
|
||
|
||
if (rigging.drawingPdf) {
|
||
document.getElementById('rigging-drawing-preview').style.display = 'block';
|
||
document.getElementById('rigging-drawing-name').textContent = rigging.drawingPdfName || 'Zeichnung.pdf';
|
||
document.getElementById('rigging-drawing-size').textContent = ((rigging.drawingPdfSize || 0) / 1024).toFixed(1) + ' KB';
|
||
} else {
|
||
document.getElementById('rigging-drawing-preview').style.display = 'none';
|
||
}
|
||
|
||
if (rigging.messeConfirmationPdf) {
|
||
document.getElementById('rigging-messe-confirmation-preview').style.display = 'block';
|
||
document.getElementById('rigging-messe-confirmation-name').textContent = rigging.messeConfirmationPdfName || 'Auftragsbestätigung.pdf';
|
||
document.getElementById('rigging-messe-confirmation-size').textContent = ((rigging.messeConfirmationPdfSize || 0) / 1024).toFixed(1) + ' KB';
|
||
} else {
|
||
document.getElementById('rigging-messe-confirmation-preview').style.display = 'none';
|
||
}
|
||
|
||
// Load rigging messe contact data
|
||
document.getElementById('edit-rigging-messe-firstname').value = rigging.messeFirstname || '';
|
||
document.getElementById('edit-rigging-messe-lastname').value = rigging.messeLastname || '';
|
||
document.getElementById('edit-rigging-messe-email').value = rigging.messeEmail || '';
|
||
document.getElementById('edit-rigging-messe-phone').value = rigging.messePhone || '';
|
||
|
||
// Load WLAN data
|
||
const wlan = job.wlan || { enabled: false, type: '', confirmationPdf: null };
|
||
document.getElementById('edit-wlan').checked = wlan.enabled || false;
|
||
document.getElementById('edit-wlan-type').value = wlan.type || '';
|
||
document.getElementById('edit-wlan-deadline').value = wlan.deadline || '';
|
||
|
||
toggleWlanFields();
|
||
|
||
if (wlan.confirmationPdf) {
|
||
document.getElementById('wlan-confirmation-preview').style.display = 'block';
|
||
document.getElementById('wlan-confirmation-name').textContent = wlan.confirmationPdfName || 'Bestätigung.pdf';
|
||
document.getElementById('wlan-confirmation-size').textContent = ((wlan.confirmationPdfSize || 0) / 1024).toFixed(1) + ' KB';
|
||
} else {
|
||
document.getElementById('wlan-confirmation-preview').style.display = 'none';
|
||
}
|
||
|
||
// Load Cleaning data
|
||
const cleaning = job.cleaning || { enabled: false, confirmationPdf: null };
|
||
document.getElementById('edit-cleaning').checked = cleaning.enabled || false;
|
||
document.getElementById('edit-cleaning-deadline').value = cleaning.deadline || '';
|
||
|
||
toggleCleaningFields();
|
||
|
||
if (cleaning.confirmationPdf) {
|
||
document.getElementById('cleaning-confirmation-preview').style.display = 'block';
|
||
document.getElementById('cleaning-confirmation-name').textContent = cleaning.confirmationPdfName || 'Bestätigung.pdf';
|
||
document.getElementById('cleaning-confirmation-size').textContent = ((cleaning.confirmationPdfSize || 0) / 1024).toFixed(1) + ' KB';
|
||
} else {
|
||
document.getElementById('cleaning-confirmation-preview').style.display = 'none';
|
||
}
|
||
|
||
// Load Stand Approval data
|
||
const standApproval = job.standApproval || { enabled: false, pdf: null };
|
||
document.getElementById('edit-stand-approval').checked = standApproval.enabled || false;
|
||
document.getElementById('edit-stand-approval-deadline').value = standApproval.deadline || '';
|
||
|
||
toggleStandApprovalFields();
|
||
|
||
if (standApproval.pdf) {
|
||
document.getElementById('stand-approval-preview').style.display = 'block';
|
||
document.getElementById('stand-approval-name').textContent = standApproval.pdfName || 'Freigabe.pdf';
|
||
document.getElementById('stand-approval-size').textContent = ((standApproval.pdfSize || 0) / 1024).toFixed(1) + ' KB';
|
||
} else {
|
||
document.getElementById('stand-approval-preview').style.display = 'none';
|
||
}
|
||
|
||
// Render dynamic lists
|
||
renderVehicles(job.vehicles || []);
|
||
renderHotels(job.hotels || []);
|
||
renderTeam(job.team || []);
|
||
|
||
// Render materials
|
||
const materials = job.materials || {};
|
||
renderMaterials('flooring', materials.bodenbelag || materials.flooring || []);
|
||
renderMaterials('standsystem', materials.standsystem || []);
|
||
renderMaterials('hardware', materials.hardware || []);
|
||
renderMaterials('furniture', materials.moebel || materials.furniture || []);
|
||
|
||
// Load lead magnets
|
||
const leadMagnets = job.leadMagnets || {};
|
||
document.getElementById('lead-flyer').checked = leadMagnets.flyer || false;
|
||
document.getElementById('lead-giveaway').checked = leadMagnets.giveaway || false;
|
||
document.getElementById('lead-tablet').checked = leadMagnets.tablet || false;
|
||
document.getElementById('lead-scanner').checked = leadMagnets.scanner || false;
|
||
document.getElementById('lead-contest').checked = leadMagnets.gewinnspiel || false;
|
||
document.getElementById('lead-demo').checked = leadMagnets.demo || false;
|
||
document.getElementById('lead-photo-booth').checked = leadMagnets.photobooth || false;
|
||
document.getElementById('lead-vending-machine').checked = leadMagnets.vendingMachine || false;
|
||
document.getElementById('lead-grab-arm').checked = leadMagnets.grabArm || false;
|
||
|
||
// Load catering data
|
||
const catering = job.catering || { baristaEnabled: false, baristaPackage: '', days: '', staff: [] };
|
||
document.getElementById('catering-barista').checked = catering.baristaEnabled || false;
|
||
document.getElementById('catering-barista-package').value = catering.baristaPackage || '';
|
||
document.getElementById('catering-days').value = catering.days || '';
|
||
toggleBaristaFields();
|
||
renderCateringStaff();
|
||
|
||
// Load drinks data
|
||
document.getElementById('catering-drinks').checked = catering.drinksEnabled || false;
|
||
document.getElementById('catering-drinks-details').value = catering.drinksDetails || '';
|
||
toggleDrinksFields();
|
||
|
||
// Load food data
|
||
document.getElementById('catering-food').checked = catering.foodEnabled || false;
|
||
document.getElementById('catering-food-details').value = catering.foodDetails || '';
|
||
toggleFoodFields();
|
||
|
||
// Load druckproduktion data
|
||
const druckproduktion = job.druckproduktion || { enabled: false, qm: '', blockout: '', forexplatten: '' };
|
||
document.getElementById('druckproduktion-enabled').checked = druckproduktion.enabled || false;
|
||
document.getElementById('druckproduktion-qm').value = druckproduktion.qm || '';
|
||
document.getElementById('druckproduktion-blockout').value = druckproduktion.blockout || '';
|
||
document.getElementById('druckproduktion-forexplatten').value = druckproduktion.forexplatten || '';
|
||
document.getElementById('druckproduktion-deadline').value = druckproduktion.deadline || '';
|
||
toggleDruckproduktion();
|
||
|
||
// Load acceptance data
|
||
const acceptance = job.acceptance || { photos: [], signature: null, remarks: '', date: '' };
|
||
document.getElementById('acceptance-remarks').value = acceptance.remarks || '';
|
||
document.getElementById('acceptance-date').value = acceptance.date || '';
|
||
|
||
// Load signature if exists
|
||
if (acceptance.signature) {
|
||
const canvas = document.getElementById('signature-canvas');
|
||
const ctx = canvas.getContext('2d');
|
||
const img = new Image();
|
||
img.onload = function() {
|
||
ctx.drawImage(img, 0, 0);
|
||
document.getElementById('signature-saved-indicator').style.display = 'block';
|
||
};
|
||
img.src = acceptance.signature;
|
||
} else {
|
||
clearSignature();
|
||
}
|
||
|
||
// Render acceptance gallery
|
||
renderAcceptanceGallery();
|
||
|
||
// Render weekly protocols
|
||
renderWeeklyProtocols();
|
||
|
||
// Load briefing data
|
||
const briefing = job.briefing || {};
|
||
|
||
// Customer briefing
|
||
if (briefing.customerDoc) {
|
||
document.getElementById('briefing-customer-preview').style.display = 'block';
|
||
document.getElementById('briefing-customer-name').textContent = briefing.customerDocName || 'Dokument.pdf';
|
||
document.getElementById('briefing-customer-size').textContent = ((briefing.customerDocSize || 0) / 1024).toFixed(1) + ' KB';
|
||
} else {
|
||
document.getElementById('briefing-customer-preview').style.display = 'none';
|
||
}
|
||
|
||
// Brandbook
|
||
if (briefing.brandDoc) {
|
||
document.getElementById('briefing-brand-preview').style.display = 'block';
|
||
document.getElementById('briefing-brand-name').textContent = briefing.brandDocName || 'Brandbook.pdf';
|
||
document.getElementById('briefing-brand-size').textContent = ((briefing.brandDocSize || 0) / 1024).toFixed(1) + ' KB';
|
||
} else {
|
||
document.getElementById('briefing-brand-preview').style.display = 'none';
|
||
}
|
||
|
||
// Standplan
|
||
if (briefing.planDoc) {
|
||
document.getElementById('briefing-plan-preview').style.display = 'block';
|
||
document.getElementById('briefing-plan-name').textContent = briefing.planDocName || 'Standplan.pdf';
|
||
document.getElementById('briefing-plan-size').textContent = ((briefing.planDocSize || 0) / 1024).toFixed(1) + ' KB';
|
||
} else {
|
||
document.getElementById('briefing-plan-preview').style.display = 'none';
|
||
}
|
||
|
||
// Additional documents
|
||
if (briefing.additionalDoc) {
|
||
document.getElementById('briefing-additional-preview').style.display = 'block';
|
||
document.getElementById('briefing-additional-name').textContent = briefing.additionalDocName || 'Dokument.pdf';
|
||
document.getElementById('briefing-additional-size').textContent = ((briefing.additionalDocSize || 0) / 1024).toFixed(1) + ' KB';
|
||
} else {
|
||
document.getElementById('briefing-additional-preview').style.display = 'none';
|
||
}
|
||
|
||
console.log('✅ Job geöffnet');
|
||
}
|
||
|
||
// Show dashboard
|
||
window.showDashboard = function() {
|
||
console.log('🏠 Zurück zum Dashboard');
|
||
|
||
const jobDetailView = document.getElementById('job-detail-view');
|
||
const calendarView = document.getElementById('calendar-view');
|
||
const dashboardView = document.getElementById('dashboard-view');
|
||
const navDashboard = document.getElementById('nav-dashboard');
|
||
const navCalendar = document.getElementById('nav-calendar');
|
||
|
||
if (jobDetailView) jobDetailView.classList.add('hidden');
|
||
if (calendarView) calendarView.classList.add('hidden');
|
||
if (dashboardView) dashboardView.classList.remove('hidden');
|
||
if (navDashboard) navDashboard.classList.add('active');
|
||
if (navCalendar) navCalendar.classList.remove('active');
|
||
|
||
currentJobId = null;
|
||
updateStats();
|
||
renderJobs();
|
||
}
|
||
|
||
// Save current job
|
||
window.saveCurrentJob = function() {
|
||
if (!currentJobId) return;
|
||
|
||
console.log('💾 Speichere Job:', currentJobId);
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
// Update job data
|
||
job.customerName = document.getElementById('edit-customer').value;
|
||
job.eventName = document.getElementById('edit-event').value;
|
||
job.location = document.getElementById('edit-location').value;
|
||
job.project = document.getElementById('edit-project').value;
|
||
job.standType = document.getElementById('edit-stand-type').value;
|
||
job.standSize = document.getElementById('edit-stand-size').value;
|
||
job.standHeight = document.getElementById('edit-stand-height').value;
|
||
job.status = document.getElementById('edit-status').value;
|
||
job.eventStart = document.getElementById('edit-event-start').value;
|
||
|
||
// Update time schedule
|
||
job.setupStart = document.getElementById('edit-setup-start').value;
|
||
job.setupEnd = document.getElementById('edit-setup-end').value;
|
||
job.eventEnd = document.getElementById('edit-event-end').value;
|
||
job.teardownStart = document.getElementById('edit-teardown-start').value;
|
||
job.teardownEnd = document.getElementById('edit-teardown-end').value;
|
||
|
||
// Update booking platform data
|
||
if (!job.bookingPlatform) {
|
||
job.bookingPlatform = {};
|
||
}
|
||
job.bookingPlatform.enabled = document.getElementById('edit-booking-platform').checked;
|
||
job.bookingPlatform.portalLink = document.getElementById('edit-booking-portal-link').value;
|
||
job.bookingPlatform.email = document.getElementById('edit-booking-email').value;
|
||
job.bookingPlatform.password = document.getElementById('edit-booking-password').value;
|
||
job.bookingPlatform.deadline = document.getElementById('edit-booking-deadline').value;
|
||
|
||
// Update power booking data
|
||
if (!job.powerBooking) {
|
||
job.powerBooking = {};
|
||
}
|
||
job.powerBooking.enabled = document.getElementById('edit-power-booking').checked;
|
||
job.powerBooking.kw = document.getElementById('edit-power-kw').value;
|
||
job.powerBooking.distributor = document.getElementById('edit-power-distributor').value;
|
||
job.powerBooking.deadline = document.getElementById('edit-power-deadline').value;
|
||
// PDF is handled separately in handlePowerConfirmationUpload
|
||
|
||
// Update rigging data
|
||
if (!job.rigging) {
|
||
job.rigging = {};
|
||
}
|
||
job.rigging.enabled = document.getElementById('edit-rigging').checked;
|
||
job.rigging.supplierFirstname = document.getElementById('edit-rigging-firstname').value;
|
||
job.rigging.supplierLastname = document.getElementById('edit-rigging-lastname').value;
|
||
job.rigging.supplierEmail = document.getElementById('edit-rigging-email').value;
|
||
job.rigging.supplierPhone = document.getElementById('edit-rigging-phone').value;
|
||
job.rigging.deadline = document.getElementById('edit-rigging-deadline').value;
|
||
job.rigging.messeFirstname = document.getElementById('edit-rigging-messe-firstname').value;
|
||
job.rigging.messeLastname = document.getElementById('edit-rigging-messe-lastname').value;
|
||
job.rigging.messeEmail = document.getElementById('edit-rigging-messe-email').value;
|
||
job.rigging.messePhone = document.getElementById('edit-rigging-messe-phone').value;
|
||
// PDFs are handled separately in upload functions
|
||
|
||
// Update WLAN data
|
||
if (!job.wlan) {
|
||
job.wlan = {};
|
||
}
|
||
job.wlan.enabled = document.getElementById('edit-wlan').checked;
|
||
job.wlan.type = document.getElementById('edit-wlan-type').value;
|
||
job.wlan.deadline = document.getElementById('edit-wlan-deadline').value;
|
||
// PDF is handled separately
|
||
|
||
// Update Cleaning data
|
||
if (!job.cleaning) {
|
||
job.cleaning = {};
|
||
}
|
||
job.cleaning.enabled = document.getElementById('edit-cleaning').checked;
|
||
job.cleaning.deadline = document.getElementById('edit-cleaning-deadline').value;
|
||
// PDF is handled separately
|
||
|
||
// Update Stand Approval data
|
||
if (!job.standApproval) {
|
||
job.standApproval = {};
|
||
}
|
||
job.standApproval.enabled = document.getElementById('edit-stand-approval').checked;
|
||
job.standApproval.deadline = document.getElementById('edit-stand-approval-deadline').value;
|
||
// PDF is handled separately
|
||
|
||
// Update lead magnets
|
||
if (!job.leadMagnets) {
|
||
job.leadMagnets = {};
|
||
}
|
||
job.leadMagnets.flyer = document.getElementById('lead-flyer').checked;
|
||
job.leadMagnets.giveaway = document.getElementById('lead-giveaway').checked;
|
||
job.leadMagnets.tablet = document.getElementById('lead-tablet').checked;
|
||
job.leadMagnets.scanner = document.getElementById('lead-scanner').checked;
|
||
job.leadMagnets.gewinnspiel = document.getElementById('lead-contest').checked;
|
||
job.leadMagnets.demo = document.getElementById('lead-demo').checked;
|
||
job.leadMagnets.photobooth = document.getElementById('lead-photo-booth').checked;
|
||
job.leadMagnets.vendingMachine = document.getElementById('lead-vending-machine').checked;
|
||
job.leadMagnets.grabArm = document.getElementById('lead-grab-arm').checked;
|
||
|
||
// Update catering data
|
||
if (!job.catering) {
|
||
job.catering = { baristaEnabled: false, baristaPackage: '', days: '', staff: [] };
|
||
}
|
||
job.catering.baristaEnabled = document.getElementById('catering-barista').checked;
|
||
job.catering.baristaPackage = document.getElementById('catering-barista-package').value;
|
||
job.catering.days = document.getElementById('catering-days').value;
|
||
job.catering.drinksEnabled = document.getElementById('catering-drinks').checked;
|
||
job.catering.drinksDetails = document.getElementById('catering-drinks-details').value;
|
||
job.catering.foodEnabled = document.getElementById('catering-food').checked;
|
||
job.catering.foodDetails = document.getElementById('catering-food-details').value;
|
||
// Staff is handled separately in updateCateringStaff
|
||
|
||
// Update druckproduktion data
|
||
if (!job.druckproduktion) {
|
||
job.druckproduktion = { enabled: false, qm: '', blockout: '', forexplatten: '' };
|
||
}
|
||
job.druckproduktion.enabled = document.getElementById('druckproduktion-enabled').checked;
|
||
job.druckproduktion.qm = document.getElementById('druckproduktion-qm').value;
|
||
job.druckproduktion.blockout = document.getElementById('druckproduktion-blockout').value;
|
||
job.druckproduktion.forexplatten = document.getElementById('druckproduktion-forexplatten').value;
|
||
job.druckproduktion.deadline = document.getElementById('druckproduktion-deadline').value;
|
||
|
||
// Update Acceptance data
|
||
if (!job.acceptance) {
|
||
job.acceptance = { photos: [], signature: null, remarks: '', date: '' };
|
||
}
|
||
job.acceptance.remarks = document.getElementById('acceptance-remarks').value;
|
||
job.acceptance.date = document.getElementById('acceptance-date').value;
|
||
// Photos and signature are handled separately in their functions
|
||
|
||
saveJobs();
|
||
showToast('✅ Job gespeichert!');
|
||
|
||
console.log('✅ Job gespeichert:', job);
|
||
}
|
||
|
||
// Delete current job
|
||
window.deleteCurrentJob = function() {
|
||
if (!currentJobId) return;
|
||
|
||
if (!confirm('Job wirklich löschen?')) return;
|
||
|
||
console.log('🗑️ Lösche Job:', currentJobId);
|
||
|
||
jobs = jobs.filter(j => j.id !== currentJobId);
|
||
saveJobs();
|
||
showDashboard();
|
||
showToast('✅ Job gelöscht!');
|
||
}
|
||
|
||
// Duplicate Current Job
|
||
window.duplicateCurrentJob = function() {
|
||
if (!currentJobId) return;
|
||
|
||
console.log('📋 Dupliziere Job:', currentJobId);
|
||
|
||
// Finde den aktuellen Job
|
||
const originalJob = jobs.find(j => j.id === currentJobId);
|
||
if (!originalJob) {
|
||
showToast('❌ Job nicht gefunden');
|
||
return;
|
||
}
|
||
|
||
// Speichere aktuellen Job zuerst
|
||
saveCurrentJob();
|
||
|
||
// Erstelle eine tiefe Kopie des Jobs
|
||
const duplicatedJob = JSON.parse(JSON.stringify(originalJob));
|
||
|
||
// Generiere neue ID
|
||
duplicatedJob.id = Date.now().toString(36) + Math.random().toString(36).substring(2);
|
||
|
||
// Aktualisiere Namen für Kopie
|
||
duplicatedJob.customerName = (duplicatedJob.customerName || 'Neuer Kunde') + ' (Kopie)';
|
||
|
||
// Aktualisiere Zeitstempel
|
||
duplicatedJob.createdAt = new Date().toISOString();
|
||
|
||
// Füge duplizierten Job zur Liste hinzu
|
||
jobs.push(duplicatedJob);
|
||
console.log('✅ Job dupliziert:', duplicatedJob.id);
|
||
|
||
// Speichere alle Jobs
|
||
saveJobs();
|
||
|
||
// Aktualisiere UI
|
||
updateStats();
|
||
renderJobs();
|
||
|
||
// Öffne den duplizierten Job
|
||
showToast('✅ Job dupliziert!');
|
||
openJob(duplicatedJob.id);
|
||
}
|
||
|
||
// Export Job to PDF
|
||
function exportJobToPDF() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) {
|
||
showToast('❌ Kein Job ausgewählt');
|
||
return;
|
||
}
|
||
|
||
console.log('📄 Exportiere Job zu PDF:', job.id);
|
||
|
||
// Zuerst Job speichern
|
||
saveCurrentJob();
|
||
|
||
// Neues Fenster für PDF-Ansicht erstellen
|
||
const printWindow = window.open('', '_blank');
|
||
|
||
// HTML-Inhalt generieren
|
||
let html = `
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Jobzettel - ${job.customerName} - ${job.eventName}</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Inter', sans-serif;
|
||
font-size: 11pt;
|
||
line-height: 1.5;
|
||
color: #111111;
|
||
padding: 20mm;
|
||
background: white;
|
||
}
|
||
|
||
.header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 30px;
|
||
padding-bottom: 20px;
|
||
border-bottom: 3px solid #FFE000;
|
||
}
|
||
|
||
.logo {
|
||
font-size: 36pt;
|
||
font-weight: 700;
|
||
color: #111111;
|
||
letter-spacing: 2px;
|
||
}
|
||
|
||
.job-title {
|
||
font-size: 18pt;
|
||
font-weight: 600;
|
||
color: #111111;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.section {
|
||
margin-bottom: 30px;
|
||
page-break-inside: avoid;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 14pt;
|
||
font-weight: 600;
|
||
color: #111111;
|
||
margin-bottom: 15px;
|
||
padding-bottom: 8px;
|
||
border-bottom: 2px solid #FFE000;
|
||
}
|
||
|
||
.info-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 10px;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.info-item {
|
||
padding: 8px;
|
||
background: #F5F5F5;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.info-label {
|
||
font-weight: 600;
|
||
font-size: 9pt;
|
||
color: #666;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 11pt;
|
||
color: #111111;
|
||
}
|
||
|
||
.list-item {
|
||
padding: 8px;
|
||
background: #F5F5F5;
|
||
border-radius: 4px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
th, td {
|
||
padding: 8px;
|
||
text-align: left;
|
||
border-bottom: 1px solid #E0E0E0;
|
||
}
|
||
|
||
th {
|
||
background: #FFE000;
|
||
font-weight: 600;
|
||
font-size: 10pt;
|
||
}
|
||
|
||
td {
|
||
font-size: 10pt;
|
||
}
|
||
|
||
.photo-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 10px;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.photo-item {
|
||
break-inside: avoid;
|
||
}
|
||
|
||
.photo-item img {
|
||
width: 100%;
|
||
height: auto;
|
||
border-radius: 4px;
|
||
border: 1px solid #E0E0E0;
|
||
}
|
||
|
||
.checkbox {
|
||
display: inline-block;
|
||
width: 12pt;
|
||
height: 12pt;
|
||
border: 2px solid #111111;
|
||
border-radius: 2px;
|
||
margin-right: 8px;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.checkbox.checked {
|
||
background: #FFE000;
|
||
}
|
||
|
||
.footer {
|
||
margin-top: 40px;
|
||
padding-top: 20px;
|
||
border-top: 2px solid #E0E0E0;
|
||
text-align: center;
|
||
font-size: 9pt;
|
||
color: #666;
|
||
}
|
||
|
||
@media print {
|
||
body {
|
||
padding: 15mm;
|
||
}
|
||
|
||
.section {
|
||
page-break-inside: avoid;
|
||
}
|
||
|
||
.no-print {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
.print-button {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
padding: 12px 24px;
|
||
background: #FFE000;
|
||
color: #111111;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
font-size: 12pt;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||
z-index: 1000;
|
||
}
|
||
|
||
.print-button:hover {
|
||
background: #FFD700;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<button class="print-button no-print" onclick="window.print()">🖨️ Drucken / Als PDF speichern</button>
|
||
|
||
<div class="header">
|
||
<div class="logo">24HRS</div>
|
||
<div>
|
||
<div class="job-title">${job.customerName || 'Neuer Kunde'}</div>
|
||
<div style="font-size: 12pt; color: #666;">${job.eventName || 'Neue Messe'}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// TITELBILD - Messestand Hauptbild (falls vorhanden)
|
||
if (job.standImage) {
|
||
html += `
|
||
<div class="section" style="text-align: center; margin-bottom: 40px; page-break-inside: avoid;">
|
||
<img src="${job.standImage}" alt="Messestand" style="max-width: 100%; height: auto; max-height: 400px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); border: 3px solid #FFE000;">
|
||
${job.standImageName ? `<p style="margin-top: 10px; font-size: 10pt; color: #666; font-style: italic;">${job.standImageName}</p>` : ''}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// 1. STAMMDATEN & TERMINE
|
||
html += `
|
||
<div class="section">
|
||
<h2 class="section-title">1. Stammdaten & Termine</h2>
|
||
<div class="info-grid">
|
||
<div class="info-item">
|
||
<div class="info-label">Kunde</div>
|
||
<div class="info-value">${job.customerName || '-'}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Messe</div>
|
||
<div class="info-value">${job.eventName || '-'}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Ort</div>
|
||
<div class="info-value">${job.location || '-'}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Projekt</div>
|
||
<div class="info-value">${job.project || '-'}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Standtyp</div>
|
||
<div class="info-value">${job.standType || '-'}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Standgröße</div>
|
||
<div class="info-value">${job.standSize || '-'}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Standhöhe</div>
|
||
<div class="info-value">${job.standHeight || '-'}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Status</div>
|
||
<div class="info-value">${job.status || '-'}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3 style="font-size: 12pt; font-weight: 600; margin: 20px 0 10px;">Termine</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Phase</th>
|
||
<th>Von</th>
|
||
<th>Bis</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Aufbau</td>
|
||
<td>${job.setupStart ? new Date(job.setupStart).toLocaleDateString('de-DE') : '-'}</td>
|
||
<td>${job.setupEnd ? new Date(job.setupEnd).toLocaleDateString('de-DE') : '-'}</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Messe</td>
|
||
<td>${job.eventStart ? new Date(job.eventStart).toLocaleDateString('de-DE') : '-'}</td>
|
||
<td>${job.eventEnd ? new Date(job.eventEnd).toLocaleDateString('de-DE') : '-'}</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Abbau</td>
|
||
<td>${job.teardownStart ? new Date(job.teardownStart).toLocaleDateString('de-DE') : '-'}</td>
|
||
<td>${job.teardownEnd ? new Date(job.teardownEnd).toLocaleDateString('de-DE') : '-'}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
${job.notes ? `
|
||
<h3 style="font-size: 12pt; font-weight: 600; margin: 20px 0 10px;">Notizen</h3>
|
||
<div class="info-item">
|
||
<div class="info-value" style="white-space: pre-wrap;">${job.notes}</div>
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
`;
|
||
|
||
// 2. ORGANISATION
|
||
html += `<div class="section">`;
|
||
html += `<h2 class="section-title">2. Organisation</h2>`;
|
||
|
||
// Buchungsplattform
|
||
if (job.bookingPlatform && job.bookingPlatform.enabled) {
|
||
html += `
|
||
<h3 style="font-size: 12pt; font-weight: 600; margin: 15px 0 10px;">Buchungsplattform</h3>
|
||
<div class="info-item">
|
||
<div class="info-label">Portal-Link</div>
|
||
<div class="info-value">${job.bookingPlatform.portalLink || '-'}</div>
|
||
</div>
|
||
<div class="info-item" style="margin-top: 8px;">
|
||
<div class="info-label">Login</div>
|
||
<div class="info-value">${job.bookingPlatform.email || '-'}</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Strombuchung
|
||
if (job.powerBooking && job.powerBooking.enabled) {
|
||
html += `
|
||
<h3 style="font-size: 12pt; font-weight: 600; margin: 15px 0 10px;">Strombuchung</h3>
|
||
<div class="info-grid">
|
||
<div class="info-item">
|
||
<div class="info-label">Leistung</div>
|
||
<div class="info-value">${job.powerBooking.kw || '-'} kW</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Verteiler</div>
|
||
<div class="info-value">${job.powerBooking.distributor || '-'}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// WLAN
|
||
if (job.wlan && job.wlan.enabled) {
|
||
html += `
|
||
<h3 style="font-size: 12pt; font-weight: 600; margin: 15px 0 10px;">WLAN</h3>
|
||
<div class="info-item">
|
||
<div class="info-label">Typ</div>
|
||
<div class="info-value">${job.wlan.type || '-'}</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Rigging
|
||
if (job.rigging && job.rigging.enabled) {
|
||
html += `
|
||
<h3 style="font-size: 12pt; font-weight: 600; margin: 15px 0 10px;">Rigging / Truss</h3>
|
||
<div class="info-item">
|
||
<div class="info-label">Lieferant</div>
|
||
<div class="info-value">${job.rigging.supplierFirstname || ''} ${job.rigging.supplierLastname || ''}</div>
|
||
</div>
|
||
${job.rigging.messeFirstname || job.rigging.messeLastname ? `
|
||
<div class="info-item" style="margin-top: 8px;">
|
||
<div class="info-label">Messe-Kontakt</div>
|
||
<div class="info-value">${job.rigging.messeFirstname || ''} ${job.rigging.messeLastname || ''}</div>
|
||
</div>
|
||
` : ''}
|
||
`;
|
||
}
|
||
|
||
// Fahrzeuge
|
||
if (job.vehicles && job.vehicles.length > 0) {
|
||
html += `
|
||
<h3 style="font-size: 12pt; font-weight: 600; margin: 15px 0 10px;">Logistik / Fahrzeuge</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Typ</th>
|
||
<th>Kennzeichen</th>
|
||
<th>Fahrer</th>
|
||
<th>Abfahrt</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
`;
|
||
job.vehicles.forEach(vehicle => {
|
||
html += `
|
||
<tr>
|
||
<td>${vehicle.type || '-'}</td>
|
||
<td>${vehicle.plate || '-'}</td>
|
||
<td>${vehicle.driver || '-'}</td>
|
||
<td>${vehicle.departure ? new Date(vehicle.departure).toLocaleDateString('de-DE') : '-'}</td>
|
||
</tr>
|
||
`;
|
||
});
|
||
html += `
|
||
</tbody>
|
||
</table>
|
||
`;
|
||
}
|
||
|
||
// Hotels
|
||
if (job.hotels && job.hotels.length > 0) {
|
||
html += `
|
||
<h3 style="font-size: 12pt; font-weight: 600; margin: 15px 0 10px;">Hotelbuchungen</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Mitarbeiter</th>
|
||
<th>Hotel</th>
|
||
<th>Check-in</th>
|
||
<th>Check-out</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
`;
|
||
job.hotels.forEach(hotel => {
|
||
html += `
|
||
<tr>
|
||
<td>${hotel.employee || '-'}</td>
|
||
<td>${hotel.name || '-'}</td>
|
||
<td>${hotel.checkin ? new Date(hotel.checkin).toLocaleDateString('de-DE') : '-'}</td>
|
||
<td>${hotel.checkout ? new Date(hotel.checkout).toLocaleDateString('de-DE') : '-'}</td>
|
||
</tr>
|
||
`;
|
||
});
|
||
html += `
|
||
</tbody>
|
||
</table>
|
||
`;
|
||
}
|
||
|
||
// Team
|
||
if (job.team && job.team.length > 0) {
|
||
html += `
|
||
<h3 style="font-size: 12pt; font-weight: 600; margin: 15px 0 10px;">Personalplanung</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Name</th>
|
||
<th>Funktion</th>
|
||
<th>Telefon</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
`;
|
||
job.team.forEach(member => {
|
||
html += `
|
||
<tr>
|
||
<td>${member.name || '-'}</td>
|
||
<td>${member.role || '-'}</td>
|
||
<td>${member.phone || '-'}</td>
|
||
</tr>
|
||
`;
|
||
});
|
||
html += `
|
||
</tbody>
|
||
</table>
|
||
`;
|
||
}
|
||
|
||
html += `</div>`; // Ende Organisation
|
||
|
||
// 3. PRODUKTION
|
||
html += `<div class="section">`;
|
||
html += `<h2 class="section-title">3. Produktion</h2>`;
|
||
|
||
// Materialien
|
||
const materialCategories = [
|
||
{ key: 'bodenbelag', title: 'Bodenbeläge' },
|
||
{ key: 'standsystem', title: 'Standsystem / Messebau-Elemente' },
|
||
{ key: 'hardware', title: 'Hardware / Technik' },
|
||
{ key: 'moebel', title: 'Möbel & Ausstattung' }
|
||
];
|
||
|
||
materialCategories.forEach(category => {
|
||
const materials = job.materials && job.materials[category.key];
|
||
if (materials && materials.length > 0) {
|
||
html += `
|
||
<h3 style="font-size: 12pt; font-weight: 600; margin: 15px 0 10px;">${category.title}</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Artikel</th>
|
||
<th>Menge</th>
|
||
<th>Einheit</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
`;
|
||
materials.forEach(material => {
|
||
html += `
|
||
<tr>
|
||
<td>${material.name || '-'}</td>
|
||
<td>${material.quantity || '-'}</td>
|
||
<td>${material.unit || '-'}</td>
|
||
</tr>
|
||
`;
|
||
});
|
||
html += `
|
||
</tbody>
|
||
</table>
|
||
`;
|
||
}
|
||
});
|
||
|
||
// Druckproduktion
|
||
if (job.druckproduktion && job.druckproduktion.enabled) {
|
||
html += `
|
||
<h3 style="font-size: 12pt; font-weight: 600; margin: 15px 0 10px;">Druckproduktion</h3>
|
||
<div class="info-grid">
|
||
<div class="info-item">
|
||
<div class="info-label">Quadratmeter</div>
|
||
<div class="info-value">${job.druckproduktion.qm || '-'} m²</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Blockout</div>
|
||
<div class="info-value">${job.druckproduktion.blockout || '-'}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Forexplatten</div>
|
||
<div class="info-value">${job.druckproduktion.forexplatten || '-'}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Catering
|
||
if (job.catering) {
|
||
let hasCatering = false;
|
||
let cateringHtml = `<h3 style="font-size: 12pt; font-weight: 600; margin: 15px 0 10px;">Catering</h3>`;
|
||
|
||
if (job.catering.baristaEnabled) {
|
||
hasCatering = true;
|
||
cateringHtml += `
|
||
<div class="info-item">
|
||
<div class="info-label">Barista-Service</div>
|
||
<div class="info-value">${job.catering.baristaPackage || '-'} (${job.catering.days || '-'} Tage)</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
if (job.catering.drinksEnabled && job.catering.drinksDetails) {
|
||
hasCatering = true;
|
||
cateringHtml += `
|
||
<div class="info-item" style="margin-top: 8px;">
|
||
<div class="info-label">Getränke</div>
|
||
<div class="info-value" style="white-space: pre-wrap;">${job.catering.drinksDetails}</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
if (job.catering.foodEnabled && job.catering.foodDetails) {
|
||
hasCatering = true;
|
||
cateringHtml += `
|
||
<div class="info-item" style="margin-top: 8px;">
|
||
<div class="info-label">Food</div>
|
||
<div class="info-value" style="white-space: pre-wrap;">${job.catering.foodDetails}</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
if (hasCatering) {
|
||
html += cateringHtml;
|
||
}
|
||
}
|
||
|
||
// Lead-Magnets
|
||
if (job.leadMagnets) {
|
||
const activeMagnets = [];
|
||
if (job.leadMagnets.flyer) activeMagnets.push('Flyer / Broschüren');
|
||
if (job.leadMagnets.giveaway) activeMagnets.push('Giveaways');
|
||
if (job.leadMagnets.tablet) activeMagnets.push('Tablet');
|
||
if (job.leadMagnets.scanner) activeMagnets.push('Badge-Scanner');
|
||
if (job.leadMagnets.gewinnspiel) activeMagnets.push('Gewinnspiel');
|
||
if (job.leadMagnets.demo) activeMagnets.push('Produkt-Demos');
|
||
|
||
if (activeMagnets.length > 0) {
|
||
html += `
|
||
<h3 style="font-size: 12pt; font-weight: 600; margin: 15px 0 10px;">Lead-Magneten</h3>
|
||
<div class="info-item">
|
||
<div class="info-value">${activeMagnets.join(', ')}</div>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
html += `</div>`; // Ende Produktion
|
||
|
||
// 4. STANDFOTOS
|
||
if (job.standGallery && job.standGallery.length > 0) {
|
||
html += `
|
||
<div class="section">
|
||
<h2 class="section-title">4. Standfotos</h2>
|
||
<div class="photo-grid">
|
||
`;
|
||
job.standGallery.forEach((photo, index) => {
|
||
html += `
|
||
<div class="photo-item">
|
||
<img src="${photo.data}" alt="Standfoto ${index + 1}">
|
||
</div>
|
||
`;
|
||
});
|
||
html += `
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// 5. DOKUMENTE (Checkliste)
|
||
if (job.checklist && job.checklist.length > 0) {
|
||
html += `
|
||
<div class="section">
|
||
<h2 class="section-title">5. Dokumente & Checkliste</h2>
|
||
`;
|
||
job.checklist.forEach(item => {
|
||
html += `
|
||
<div class="list-item">
|
||
<span class="checkbox ${item.checked ? 'checked' : ''}"></span>
|
||
${item.text}
|
||
</div>
|
||
`;
|
||
});
|
||
html += `
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Footer
|
||
html += `
|
||
<div class="footer">
|
||
<div>24HRS Messebau Projektmanagement</div>
|
||
<div style="margin-top: 8px;">Erstellt am: ${new Date().toLocaleDateString('de-DE', {
|
||
day: '2-digit',
|
||
month: '2-digit',
|
||
year: 'numeric',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
})}</div>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`;
|
||
|
||
// HTML in neues Fenster schreiben
|
||
printWindow.document.write(html);
|
||
printWindow.document.close();
|
||
|
||
// Warten bis Bilder geladen sind, dann Druckdialog öffnen
|
||
printWindow.onload = function() {
|
||
setTimeout(() => {
|
||
printWindow.focus();
|
||
}, 500);
|
||
};
|
||
|
||
showToast('✅ PDF-Ansicht geöffnet');
|
||
}
|
||
|
||
// Tab switching
|
||
function switchTab(tabIndex) {
|
||
// Convert to number if string
|
||
const targetIndex = parseInt(tabIndex, 10);
|
||
|
||
document.querySelectorAll('.tab-btn').forEach((btn, index) => {
|
||
if (index === targetIndex) {
|
||
btn.classList.add('active');
|
||
} else {
|
||
btn.classList.remove('active');
|
||
}
|
||
});
|
||
|
||
document.querySelectorAll('.tab-pane').forEach((pane, index) => {
|
||
if (index === targetIndex) {
|
||
pane.classList.add('active');
|
||
} else {
|
||
pane.classList.remove('active');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Hotels
|
||
function addHotel() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (!job.hotels) job.hotels = [];
|
||
|
||
job.hotels.push({
|
||
id: Date.now().toString(36) + Math.random().toString(36).substr(2),
|
||
employee: '',
|
||
hotel: '',
|
||
checkin: '',
|
||
checkout: '',
|
||
confirmationFile: null,
|
||
confirmationFileName: ''
|
||
});
|
||
|
||
renderHotels(job.hotels);
|
||
saveJobs();
|
||
}
|
||
|
||
function renderHotels(hotels) {
|
||
const container = document.getElementById('hotels-container');
|
||
if (!hotels || hotels.length === 0) {
|
||
container.innerHTML = '<p style="color: var(--text-secondary); padding: 12px;">Keine Hotels hinzugefügt</p>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = hotels.map((hotel, index) => `
|
||
<div class="list-item">
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>Mitarbeiter</label>
|
||
<input type="text" value="${hotel.employee || ''}" onchange="updateHotel(${index}, 'employee', this.value)">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Hotel</label>
|
||
<input type="text" value="${hotel.hotel || ''}" onchange="updateHotel(${index}, 'hotel', this.value)">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Check-in</label>
|
||
<input type="date" value="${hotel.checkin || ''}" onchange="updateHotel(${index}, 'checkin', this.value)">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Check-out</label>
|
||
<input type="date" value="${hotel.checkout || ''}" onchange="updateHotel(${index}, 'checkout', this.value)">
|
||
</div>
|
||
</div>
|
||
<button class="icon-btn danger" onclick="removeHotel(${index})" style="margin-top: 8px;">
|
||
<i class="fas fa-trash"></i> Entfernen
|
||
</button>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function updateHotel(index, field, value) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.hotels[index]) return;
|
||
job.hotels[index][field] = value;
|
||
saveJobs();
|
||
}
|
||
|
||
function removeHotel(index) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
job.hotels.splice(index, 1);
|
||
renderHotels(job.hotels);
|
||
saveJobs();
|
||
}
|
||
|
||
// Team
|
||
function addTeamMember() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (!job.team) job.team = [];
|
||
|
||
job.team.push({
|
||
id: Date.now().toString(36) + Math.random().toString(36).substr(2),
|
||
name: '',
|
||
role: '',
|
||
phone: ''
|
||
});
|
||
|
||
renderTeam(job.team);
|
||
saveJobs();
|
||
}
|
||
|
||
function renderTeam(team) {
|
||
const container = document.getElementById('team-container');
|
||
const countEl = document.getElementById('team-count');
|
||
|
||
countEl.textContent = `(${team ? team.length : 0} Personen)`;
|
||
|
||
if (!team || team.length === 0) {
|
||
container.innerHTML = '<p style="color: var(--text-secondary); padding: 12px;">Keine Mitarbeiter hinzugefügt</p>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = team.map((member, index) => `
|
||
<div class="list-item">
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>Name</label>
|
||
<input type="text" value="${member.name || ''}" onchange="updateTeamMember(${index}, 'name', this.value)">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Funktion</label>
|
||
<input type="text" value="${member.role || ''}" onchange="updateTeamMember(${index}, 'role', this.value)">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Telefon</label>
|
||
<input type="tel" value="${member.phone || ''}" onchange="updateTeamMember(${index}, 'phone', this.value)">
|
||
</div>
|
||
</div>
|
||
<button class="icon-btn danger" onclick="removeTeamMember(${index})" style="margin-top: 8px;">
|
||
<i class="fas fa-trash"></i> Entfernen
|
||
</button>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function updateTeamMember(index, field, value) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.team[index]) return;
|
||
job.team[index][field] = value;
|
||
saveJobs();
|
||
}
|
||
|
||
function removeTeamMember(index) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
job.team.splice(index, 1);
|
||
renderTeam(job.team);
|
||
saveJobs();
|
||
}
|
||
|
||
// Materials
|
||
function addMaterial(category) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (!job.materials) job.materials = {};
|
||
if (!job.materials[category]) job.materials[category] = [];
|
||
|
||
const newMaterial = {
|
||
id: Date.now().toString(36) + Math.random().toString(36).substr(2),
|
||
name: '',
|
||
quantity: '',
|
||
unit: 'Stk'
|
||
};
|
||
|
||
if (category === 'flooring') {
|
||
newMaterial.winkelleistenLFM = '';
|
||
newMaterial.dekorQM = '';
|
||
}
|
||
|
||
job.materials[category].push(newMaterial);
|
||
renderMaterials(category, job.materials[category]);
|
||
saveJobs();
|
||
}
|
||
|
||
function renderMaterials(category, materials) {
|
||
const container = document.getElementById(`${category}-container`);
|
||
|
||
// Null-Check: Element muss existieren
|
||
if (!container) {
|
||
console.warn(`⚠️ Container nicht gefunden: ${category}-container`);
|
||
return;
|
||
}
|
||
|
||
if (!materials || materials.length === 0) {
|
||
container.innerHTML = '<p style="color: var(--text-secondary); padding: 12px;">Keine Materialien</p>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = materials.map((material, index) => {
|
||
let fieldsHTML = `
|
||
<div class="form-group">
|
||
<label>Artikel</label>
|
||
<input type="text" value="${material.name || ''}" onchange="updateMaterial('${category}', ${index}, 'name', this.value)">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Menge</label>
|
||
<input type="number" value="${material.quantity || ''}" onchange="updateMaterial('${category}', ${index}, 'quantity', this.value)">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Einheit</label>
|
||
<select onchange="updateMaterial('${category}', ${index}, 'unit', this.value)">
|
||
<option value="Stk" ${material.unit === 'Stk' ? 'selected' : ''}>Stk</option>
|
||
<option value="m" ${material.unit === 'm' ? 'selected' : ''}>m</option>
|
||
<option value="m²" ${material.unit === 'm²' ? 'selected' : ''}>m²</option>
|
||
<option value="kg" ${material.unit === 'kg' ? 'selected' : ''}>kg</option>
|
||
<option value="Palette" ${material.unit === 'Palette' ? 'selected' : ''}>Palette</option>
|
||
</select>
|
||
</div>
|
||
`;
|
||
|
||
if (category === 'flooring') {
|
||
fieldsHTML += `
|
||
<div class="form-group">
|
||
<label>Winkelleisten (LFM)</label>
|
||
<input type="number" step="0.01" value="${material.winkelleistenLFM || ''}" onchange="updateMaterial('${category}', ${index}, 'winkelleistenLFM', this.value)">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Dekor (m²)</label>
|
||
<input type="number" step="0.01" value="${material.dekorQM || ''}" onchange="updateMaterial('${category}', ${index}, 'dekorQM', this.value)">
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
return `
|
||
<div class="list-item">
|
||
<div class="form-grid">
|
||
${fieldsHTML}
|
||
</div>
|
||
<button class="icon-btn danger" onclick="removeMaterial('${category}', ${index})" style="margin-top: 8px;">
|
||
<i class="fas fa-trash"></i> Entfernen
|
||
</button>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
function updateMaterial(category, index, field, value) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.materials[category][index]) return;
|
||
job.materials[category][index][field] = value;
|
||
saveJobs();
|
||
}
|
||
|
||
function removeMaterial(category, index) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
job.materials[category].splice(index, 1);
|
||
renderMaterials(category, job.materials[category]);
|
||
saveJobs();
|
||
}
|
||
|
||
// Druckproduktion functions
|
||
function toggleDruckproduktion() {
|
||
const checkbox = document.getElementById('druckproduktion-enabled');
|
||
const fields = document.getElementById('druckproduktion-fields');
|
||
fields.style.display = checkbox.checked ? 'block' : 'none';
|
||
}
|
||
|
||
// Catering functions
|
||
function toggleBaristaFields() {
|
||
const checkbox = document.getElementById('catering-barista');
|
||
const fields = document.getElementById('barista-fields');
|
||
fields.style.display = checkbox.checked ? 'block' : 'none';
|
||
}
|
||
|
||
function toggleDrinksFields() {
|
||
const checkbox = document.getElementById('catering-drinks');
|
||
const fields = document.getElementById('drinks-fields');
|
||
fields.style.display = checkbox.checked ? 'block' : 'none';
|
||
}
|
||
|
||
function toggleFoodFields() {
|
||
const checkbox = document.getElementById('catering-food');
|
||
const fields = document.getElementById('food-fields');
|
||
fields.style.display = checkbox.checked ? 'block' : 'none';
|
||
}
|
||
|
||
function addCateringStaff() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (!job.catering) {
|
||
job.catering = {
|
||
baristaEnabled: false,
|
||
baristaPackage: '',
|
||
days: '',
|
||
staff: []
|
||
};
|
||
}
|
||
|
||
if (!job.catering.staff) {
|
||
job.catering.staff = [];
|
||
}
|
||
|
||
const newStaff = {
|
||
id: Date.now().toString(36) + Math.random().toString(36).substr(2),
|
||
firstname: '',
|
||
lastname: '',
|
||
email: ''
|
||
};
|
||
|
||
job.catering.staff.push(newStaff);
|
||
renderCateringStaff();
|
||
saveJobs();
|
||
}
|
||
|
||
function renderCateringStaff() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.catering || !job.catering.staff) {
|
||
document.getElementById('catering-staff-container').innerHTML = '<p style="color: #999; font-size: 14px;">Noch kein Personal hinzugefügt</p>';
|
||
return;
|
||
}
|
||
|
||
const container = document.getElementById('catering-staff-container');
|
||
|
||
if (job.catering.staff.length === 0) {
|
||
container.innerHTML = '<p style="color: #999; font-size: 14px;">Noch kein Personal hinzugefügt</p>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = job.catering.staff.map((person, index) => `
|
||
<div class="list-item">
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>Vorname</label>
|
||
<input type="text" value="${person.firstname || ''}" onchange="updateCateringStaff(${index}, 'firstname', this.value)">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Nachname</label>
|
||
<input type="text" value="${person.lastname || ''}" onchange="updateCateringStaff(${index}, 'lastname', this.value)">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>E-Mail</label>
|
||
<input type="email" value="${person.email || ''}" onchange="updateCateringStaff(${index}, 'email', this.value)">
|
||
</div>
|
||
</div>
|
||
<button class="icon-btn danger" onclick="removeCateringStaff(${index})" style="margin-top: 8px;">
|
||
<i class="fas fa-trash"></i> Entfernen
|
||
</button>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function updateCateringStaff(index, field, value) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.catering || !job.catering.staff) return;
|
||
|
||
job.catering.staff[index][field] = value;
|
||
saveJobs();
|
||
}
|
||
|
||
function removeCateringStaff(index) {
|
||
if (!confirm('Person wirklich entfernen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.catering || !job.catering.staff) return;
|
||
|
||
job.catering.staff.splice(index, 1);
|
||
renderCateringStaff();
|
||
saveJobs();
|
||
}
|
||
|
||
// Checklist
|
||
function addChecklistItem() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
const text = prompt('Neue Aufgabe:');
|
||
if (!text) return;
|
||
|
||
if (!job.checklist) job.checklist = [];
|
||
|
||
job.checklist.push({
|
||
id: Date.now().toString(36) + Math.random().toString(36).substr(2),
|
||
text: text,
|
||
completed: false,
|
||
custom: true
|
||
});
|
||
|
||
renderChecklist(job.checklist);
|
||
saveJobs();
|
||
}
|
||
|
||
function renderChecklist(checklist) {
|
||
const container = document.getElementById('checklist-container');
|
||
|
||
if (!checklist || checklist.length === 0) {
|
||
container.innerHTML = '<p style="color: var(--text-secondary); padding: 12px;">Keine Aufgaben</p>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = checklist.map((item, index) => `
|
||
<div class="checklist-item ${item.completed ? 'completed' : ''}">
|
||
<input type="checkbox" id="check-${index}" ${item.completed ? 'checked' : ''} onchange="toggleChecklistItem(${index})">
|
||
<label for="check-${index}">${item.text}</label>
|
||
${item.custom ? `<button class="icon-btn danger" onclick="removeChecklistItem(${index})"><i class="fas fa-trash"></i></button>` : ''}
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function toggleChecklistItem(index) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.checklist[index]) return;
|
||
job.checklist[index].completed = !job.checklist[index].completed;
|
||
renderChecklist(job.checklist);
|
||
saveJobs();
|
||
}
|
||
|
||
function removeChecklistItem(index) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
job.checklist.splice(index, 1);
|
||
renderChecklist(job.checklist);
|
||
saveJobs();
|
||
}
|
||
|
||
// Stand image upload functions
|
||
function handleStandImageUpload(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
// Check file size (max 2MB)
|
||
if (file.size > 2 * 1024 * 1024) {
|
||
showToast('❌ Bild zu groß! Max. 2 MB');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
// Check file type
|
||
if (!file.type.startsWith('image/')) {
|
||
showToast('❌ Nur Bilddateien erlaubt!');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
job.standImage = e.target.result;
|
||
job.standImageName = file.name;
|
||
|
||
// Show preview
|
||
const preview = document.getElementById('stand-image-preview');
|
||
const previewImg = document.getElementById('stand-image-preview-img');
|
||
|
||
previewImg.src = e.target.result;
|
||
preview.style.display = 'block';
|
||
|
||
saveJobs();
|
||
showToast('✅ Bild hochgeladen');
|
||
console.log('✅ Messestand-Bild hochgeladen:', file.name);
|
||
};
|
||
|
||
reader.onerror = function() {
|
||
showToast('❌ Fehler beim Hochladen');
|
||
console.error('❌ Fehler beim Lesen der Bilddatei');
|
||
};
|
||
|
||
reader.readAsDataURL(file);
|
||
}
|
||
|
||
// Toggle booking platform fields
|
||
function toggleBookingPlatformFields() {
|
||
const checkbox = document.getElementById('edit-booking-platform');
|
||
const fields = document.getElementById('booking-platform-fields');
|
||
|
||
if (checkbox.checked) {
|
||
fields.style.display = 'block';
|
||
} else {
|
||
fields.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// Toggle power booking fields
|
||
function togglePowerBookingFields() {
|
||
const checkbox = document.getElementById('edit-power-booking');
|
||
const fields = document.getElementById('power-booking-fields');
|
||
|
||
if (checkbox.checked) {
|
||
fields.style.display = 'block';
|
||
} else {
|
||
fields.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// Power confirmation PDF upload
|
||
function handlePowerConfirmationUpload(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
// Check file size (max 5MB)
|
||
if (file.size > 5 * 1024 * 1024) {
|
||
showToast('❌ PDF zu groß! Max. 5 MB');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
// Check file type
|
||
if (file.type !== 'application/pdf') {
|
||
showToast('❌ Nur PDF-Dateien erlaubt!');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (!job.powerBooking) {
|
||
job.powerBooking = {};
|
||
}
|
||
|
||
job.powerBooking.confirmationPdf = e.target.result;
|
||
job.powerBooking.confirmationPdfName = file.name;
|
||
job.powerBooking.confirmationPdfSize = file.size;
|
||
|
||
// Show preview
|
||
document.getElementById('power-confirmation-preview').style.display = 'block';
|
||
document.getElementById('power-confirmation-name').textContent = file.name;
|
||
document.getElementById('power-confirmation-size').textContent = (file.size / 1024).toFixed(1) + ' KB';
|
||
|
||
saveJobs();
|
||
showToast('✅ PDF hochgeladen');
|
||
console.log('✅ Strombuchungs-PDF hochgeladen:', file.name);
|
||
};
|
||
|
||
reader.onerror = function() {
|
||
showToast('❌ Fehler beim Hochladen');
|
||
console.error('❌ Fehler beim Lesen der PDF-Datei');
|
||
};
|
||
|
||
reader.readAsDataURL(file);
|
||
}
|
||
|
||
function downloadPowerConfirmation() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.powerBooking || !job.powerBooking.confirmationPdf) return;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = job.powerBooking.confirmationPdf;
|
||
link.download = job.powerBooking.confirmationPdfName || 'strombuchung.pdf';
|
||
link.click();
|
||
showToast('✅ PDF heruntergeladen');
|
||
}
|
||
|
||
function removePowerConfirmation() {
|
||
if (!confirm('PDF wirklich entfernen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.powerBooking) return;
|
||
|
||
job.powerBooking.confirmationPdf = null;
|
||
job.powerBooking.confirmationPdfName = null;
|
||
job.powerBooking.confirmationPdfSize = null;
|
||
|
||
document.getElementById('power-confirmation-preview').style.display = 'none';
|
||
document.getElementById('edit-power-confirmation-upload').value = '';
|
||
|
||
saveJobs();
|
||
showToast('✅ PDF entfernt');
|
||
}
|
||
|
||
// Toggle rigging fields
|
||
function toggleRiggingFields() {
|
||
const checkbox = document.getElementById('edit-rigging');
|
||
const fields = document.getElementById('rigging-fields');
|
||
|
||
if (checkbox.checked) {
|
||
fields.style.display = 'block';
|
||
} else {
|
||
fields.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// Rigging quote PDF upload
|
||
function handleRiggingQuoteUpload(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
if (file.size > 5 * 1024 * 1024) {
|
||
showToast('❌ PDF zu groß! Max. 5 MB');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
if (file.type !== 'application/pdf') {
|
||
showToast('❌ Nur PDF-Dateien erlaubt!');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (!job.rigging) {
|
||
job.rigging = {};
|
||
}
|
||
|
||
job.rigging.quotePdf = e.target.result;
|
||
job.rigging.quotePdfName = file.name;
|
||
job.rigging.quotePdfSize = file.size;
|
||
|
||
document.getElementById('rigging-quote-preview').style.display = 'block';
|
||
document.getElementById('rigging-quote-name').textContent = file.name;
|
||
document.getElementById('rigging-quote-size').textContent = (file.size / 1024).toFixed(1) + ' KB';
|
||
|
||
saveJobs();
|
||
showToast('✅ Angebot hochgeladen');
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
|
||
function downloadRiggingQuote() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.rigging || !job.rigging.quotePdf) return;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = job.rigging.quotePdf;
|
||
link.download = job.rigging.quotePdfName || 'rigging-angebot.pdf';
|
||
link.click();
|
||
showToast('✅ Angebot heruntergeladen');
|
||
}
|
||
|
||
function removeRiggingQuote() {
|
||
if (!confirm('Angebot wirklich entfernen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.rigging) return;
|
||
|
||
job.rigging.quotePdf = null;
|
||
job.rigging.quotePdfName = null;
|
||
job.rigging.quotePdfSize = null;
|
||
|
||
document.getElementById('rigging-quote-preview').style.display = 'none';
|
||
document.getElementById('edit-rigging-quote-upload').value = '';
|
||
|
||
saveJobs();
|
||
showToast('✅ Angebot entfernt');
|
||
}
|
||
|
||
// Rigging drawing PDF upload
|
||
function handleRiggingDrawingUpload(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
if (file.size > 5 * 1024 * 1024) {
|
||
showToast('❌ PDF zu groß! Max. 5 MB');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
if (file.type !== 'application/pdf') {
|
||
showToast('❌ Nur PDF-Dateien erlaubt!');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (!job.rigging) {
|
||
job.rigging = {};
|
||
}
|
||
|
||
job.rigging.drawingPdf = e.target.result;
|
||
job.rigging.drawingPdfName = file.name;
|
||
job.rigging.drawingPdfSize = file.size;
|
||
|
||
document.getElementById('rigging-drawing-preview').style.display = 'block';
|
||
document.getElementById('rigging-drawing-name').textContent = file.name;
|
||
document.getElementById('rigging-drawing-size').textContent = (file.size / 1024).toFixed(1) + ' KB';
|
||
|
||
saveJobs();
|
||
showToast('✅ Zeichnung hochgeladen');
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
|
||
function downloadRiggingDrawing() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.rigging || !job.rigging.drawingPdf) return;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = job.rigging.drawingPdf;
|
||
link.download = job.rigging.drawingPdfName || 'rigging-zeichnung.pdf';
|
||
link.click();
|
||
showToast('✅ Zeichnung heruntergeladen');
|
||
}
|
||
|
||
function removeRiggingDrawing() {
|
||
if (!confirm('Zeichnung wirklich entfernen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.rigging) return;
|
||
|
||
job.rigging.drawingPdf = null;
|
||
job.rigging.drawingPdfName = null;
|
||
job.rigging.drawingPdfSize = null;
|
||
|
||
document.getElementById('rigging-drawing-preview').style.display = 'none';
|
||
document.getElementById('edit-rigging-drawing-upload').value = '';
|
||
|
||
saveJobs();
|
||
showToast('✅ Zeichnung entfernt');
|
||
}
|
||
|
||
function handleRiggingMesseConfirmationUpload(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
if (file.size > 5 * 1024 * 1024) {
|
||
showToast('❌ PDF zu groß! Max. 5 MB');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
if (file.type !== 'application/pdf') {
|
||
showToast('❌ Nur PDF-Dateien erlaubt!');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (!job.rigging) {
|
||
job.rigging = {};
|
||
}
|
||
|
||
job.rigging.messeConfirmationPdf = e.target.result;
|
||
job.rigging.messeConfirmationPdfName = file.name;
|
||
job.rigging.messeConfirmationPdfSize = file.size;
|
||
|
||
document.getElementById('rigging-messe-confirmation-preview').style.display = 'block';
|
||
document.getElementById('rigging-messe-confirmation-name').textContent = file.name;
|
||
document.getElementById('rigging-messe-confirmation-size').textContent = (file.size / 1024).toFixed(1) + ' KB';
|
||
|
||
saveJobs();
|
||
showToast('✅ Auftragsbestätigung hochgeladen');
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
|
||
function downloadRiggingMesseConfirmation() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.rigging || !job.rigging.messeConfirmationPdf) return;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = job.rigging.messeConfirmationPdf;
|
||
link.download = job.rigging.messeConfirmationPdfName || 'auftragsbestaetigung-messe.pdf';
|
||
link.click();
|
||
showToast('✅ Auftragsbestätigung heruntergeladen');
|
||
}
|
||
|
||
function removeRiggingMesseConfirmation() {
|
||
if (!confirm('Auftragsbestätigung wirklich entfernen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.rigging) return;
|
||
|
||
job.rigging.messeConfirmationPdf = null;
|
||
job.rigging.messeConfirmationPdfName = null;
|
||
job.rigging.messeConfirmationPdfSize = null;
|
||
|
||
document.getElementById('rigging-messe-confirmation-preview').style.display = 'none';
|
||
document.getElementById('edit-rigging-messe-confirmation-upload').value = '';
|
||
|
||
saveJobs();
|
||
showToast('✅ Auftragsbestätigung entfernt');
|
||
}
|
||
|
||
// Vehicles / Logistics
|
||
function addVehicle() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (!job.vehicles) job.vehicles = [];
|
||
|
||
job.vehicles.push({
|
||
id: Date.now().toString(36) + Math.random().toString(36).substr(2),
|
||
type: 'Eigene FZGs',
|
||
license: '',
|
||
driver: '',
|
||
departure: '',
|
||
return: '',
|
||
confirmationPdf: null,
|
||
confirmationPdfName: null,
|
||
confirmationPdfSize: null
|
||
});
|
||
|
||
renderVehicles(job.vehicles);
|
||
saveJobs();
|
||
}
|
||
|
||
function renderVehicles(vehicles) {
|
||
const container = document.getElementById('vehicles-container');
|
||
if (!vehicles || vehicles.length === 0) {
|
||
container.innerHTML = '<p style="color: var(--text-secondary); padding: 12px;">Keine Fahrzeuge hinzugefügt</p>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = vehicles.map((vehicle, index) => `
|
||
<div class="list-item" style="margin-bottom: 16px;">
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>Fahrzeug</label>
|
||
<select onchange="updateVehicle(${index}, 'type', this.value)" style="padding: 8px; border: 1px solid #E0E0E0; border-radius: 4px; font-size: 14px; width: 100%;">
|
||
<option value="Eigene FZGs" ${vehicle.type === 'Eigene FZGs' ? 'selected' : ''}>Eigene FZGs</option>
|
||
<option value="Arndt" ${vehicle.type === 'Arndt' ? 'selected' : ''}>Arndt</option>
|
||
<option value="AVIS" ${vehicle.type === 'AVIS' ? 'selected' : ''}>AVIS</option>
|
||
<option value="Andere" ${vehicle.type === 'Andere' ? 'selected' : ''}>Andere</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Kennzeichen</label>
|
||
<input type="text" value="${vehicle.license || ''}" onchange="updateVehicle(${index}, 'license', this.value)" placeholder="z.B. B-AB 1234">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Fahrer</label>
|
||
<input type="text" value="${vehicle.driver || ''}" onchange="updateVehicle(${index}, 'driver', this.value)" placeholder="Name des Fahrers">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Abfahrt</label>
|
||
<input type="datetime-local" value="${vehicle.departure || ''}" onchange="updateVehicle(${index}, 'departure', this.value)">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Rückkehr</label>
|
||
<input type="datetime-local" value="${vehicle.return || ''}" onchange="updateVehicle(${index}, 'return', this.value)">
|
||
</div>
|
||
|
||
<div class="form-group" style="grid-column: 1 / -1;">
|
||
<label>Buchungsbestätigung (PDF)</label>
|
||
<input type="file" id="vehicle-confirmation-upload-${index}" accept=".pdf" onchange="handleVehicleConfirmationUpload(event, ${index})" style="display: none;">
|
||
<button class="btn btn-secondary btn-small" onclick="document.getElementById('vehicle-confirmation-upload-${index}').click()">
|
||
<i class="fas fa-upload"></i> PDF hochladen
|
||
</button>
|
||
|
||
${vehicle.confirmationPdf ? `
|
||
<div style="margin-top: 12px; padding: 12px; background: #F5F5F5; border-radius: 6px; border: 2px solid #FFE000;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<i class="fas fa-file-pdf" style="color: #E74C3C; font-size: 24px;"></i>
|
||
<div>
|
||
<div style="font-weight: 600; color: #111111;">${vehicle.confirmationPdfName || 'Bestätigung.pdf'}</div>
|
||
<div style="font-size: 12px; color: #666;">${((vehicle.confirmationPdfSize || 0) / 1024).toFixed(1)} KB</div>
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<button class="btn btn-secondary btn-small" onclick="downloadVehicleConfirmation(${index})" title="Herunterladen">
|
||
<i class="fas fa-download"></i>
|
||
</button>
|
||
<button class="btn btn-secondary btn-small" onclick="removeVehicleConfirmation(${index})" title="Entfernen">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
</div>
|
||
<button class="icon-btn danger" onclick="removeVehicle(${index})" style="margin-top: 8px;">
|
||
<i class="fas fa-trash"></i> Fahrzeug entfernen
|
||
</button>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function updateVehicle(index, field, value) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.vehicles || !job.vehicles[index]) return;
|
||
job.vehicles[index][field] = value;
|
||
saveJobs();
|
||
}
|
||
|
||
function removeVehicle(index) {
|
||
if (!confirm('Fahrzeug wirklich entfernen?')) return;
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
job.vehicles.splice(index, 1);
|
||
renderVehicles(job.vehicles);
|
||
saveJobs();
|
||
}
|
||
|
||
function handleVehicleConfirmationUpload(event, index) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
if (file.size > 5 * 1024 * 1024) {
|
||
showToast('❌ PDF zu groß! Max. 5 MB');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
if (file.type !== 'application/pdf') {
|
||
showToast('❌ Nur PDF-Dateien erlaubt!');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.vehicles || !job.vehicles[index]) return;
|
||
|
||
job.vehicles[index].confirmationPdf = e.target.result;
|
||
job.vehicles[index].confirmationPdfName = file.name;
|
||
job.vehicles[index].confirmationPdfSize = file.size;
|
||
|
||
renderVehicles(job.vehicles);
|
||
saveJobs();
|
||
showToast('✅ PDF hochgeladen');
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
|
||
function downloadVehicleConfirmation(index) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.vehicles || !job.vehicles[index] || !job.vehicles[index].confirmationPdf) return;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = job.vehicles[index].confirmationPdf;
|
||
link.download = job.vehicles[index].confirmationPdfName || 'fahrzeug-bestaetigung.pdf';
|
||
link.click();
|
||
showToast('✅ PDF heruntergeladen');
|
||
}
|
||
|
||
function removeVehicleConfirmation(index) {
|
||
if (!confirm('PDF wirklich entfernen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.vehicles || !job.vehicles[index]) return;
|
||
|
||
job.vehicles[index].confirmationPdf = null;
|
||
job.vehicles[index].confirmationPdfName = null;
|
||
job.vehicles[index].confirmationPdfSize = null;
|
||
|
||
renderVehicles(job.vehicles);
|
||
saveJobs();
|
||
showToast('✅ PDF entfernt');
|
||
}
|
||
|
||
// WLAN functions
|
||
function toggleWlanFields() {
|
||
const checkbox = document.getElementById('edit-wlan');
|
||
const fields = document.getElementById('wlan-fields');
|
||
|
||
if (checkbox.checked) {
|
||
fields.style.display = 'block';
|
||
} else {
|
||
fields.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
function handleWlanConfirmationUpload(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
if (file.size > 5 * 1024 * 1024) {
|
||
showToast('❌ PDF zu groß! Max. 5 MB');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
if (file.type !== 'application/pdf') {
|
||
showToast('❌ Nur PDF-Dateien erlaubt!');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (!job.wlan) {
|
||
job.wlan = {};
|
||
}
|
||
|
||
job.wlan.confirmationPdf = e.target.result;
|
||
job.wlan.confirmationPdfName = file.name;
|
||
job.wlan.confirmationPdfSize = file.size;
|
||
|
||
document.getElementById('wlan-confirmation-preview').style.display = 'block';
|
||
document.getElementById('wlan-confirmation-name').textContent = file.name;
|
||
document.getElementById('wlan-confirmation-size').textContent = (file.size / 1024).toFixed(1) + ' KB';
|
||
|
||
saveJobs();
|
||
showToast('✅ PDF hochgeladen');
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
|
||
function downloadWlanConfirmation() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.wlan || !job.wlan.confirmationPdf) return;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = job.wlan.confirmationPdf;
|
||
link.download = job.wlan.confirmationPdfName || 'wlan-bestaetigung.pdf';
|
||
link.click();
|
||
showToast('✅ PDF heruntergeladen');
|
||
}
|
||
|
||
function removeWlanConfirmation() {
|
||
if (!confirm('PDF wirklich entfernen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.wlan) return;
|
||
|
||
job.wlan.confirmationPdf = null;
|
||
job.wlan.confirmationPdfName = null;
|
||
job.wlan.confirmationPdfSize = null;
|
||
|
||
document.getElementById('wlan-confirmation-preview').style.display = 'none';
|
||
document.getElementById('edit-wlan-confirmation-upload').value = '';
|
||
|
||
saveJobs();
|
||
showToast('✅ PDF entfernt');
|
||
}
|
||
|
||
// Cleaning service functions
|
||
function toggleCleaningFields() {
|
||
const checkbox = document.getElementById('edit-cleaning');
|
||
const fields = document.getElementById('cleaning-fields');
|
||
|
||
if (checkbox.checked) {
|
||
fields.style.display = 'block';
|
||
} else {
|
||
fields.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
function handleCleaningConfirmationUpload(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
if (file.size > 5 * 1024 * 1024) {
|
||
showToast('❌ PDF zu groß! Max. 5 MB');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
if (file.type !== 'application/pdf') {
|
||
showToast('❌ Nur PDF-Dateien erlaubt!');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (!job.cleaning) {
|
||
job.cleaning = {};
|
||
}
|
||
|
||
job.cleaning.confirmationPdf = e.target.result;
|
||
job.cleaning.confirmationPdfName = file.name;
|
||
job.cleaning.confirmationPdfSize = file.size;
|
||
|
||
document.getElementById('cleaning-confirmation-preview').style.display = 'block';
|
||
document.getElementById('cleaning-confirmation-name').textContent = file.name;
|
||
document.getElementById('cleaning-confirmation-size').textContent = (file.size / 1024).toFixed(1) + ' KB';
|
||
|
||
saveJobs();
|
||
showToast('✅ PDF hochgeladen');
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
|
||
function downloadCleaningConfirmation() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.cleaning || !job.cleaning.confirmationPdf) return;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = job.cleaning.confirmationPdf;
|
||
link.download = job.cleaning.confirmationPdfName || 'reinigung-bestaetigung.pdf';
|
||
link.click();
|
||
showToast('✅ PDF heruntergeladen');
|
||
}
|
||
|
||
function removeCleaningConfirmation() {
|
||
if (!confirm('PDF wirklich entfernen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.cleaning) return;
|
||
|
||
job.cleaning.confirmationPdf = null;
|
||
job.cleaning.confirmationPdfName = null;
|
||
job.cleaning.confirmationPdfSize = null;
|
||
|
||
document.getElementById('cleaning-confirmation-preview').style.display = 'none';
|
||
document.getElementById('edit-cleaning-confirmation-upload').value = '';
|
||
|
||
saveJobs();
|
||
showToast('✅ PDF entfernt');
|
||
}
|
||
|
||
// ========== Standfreigabe Functions ==========
|
||
|
||
function toggleStandApprovalFields() {
|
||
const checkbox = document.getElementById('edit-stand-approval');
|
||
const fields = document.getElementById('stand-approval-fields');
|
||
|
||
if (checkbox.checked) {
|
||
fields.style.display = 'block';
|
||
} else {
|
||
fields.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
function handleStandApprovalUpload(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
if (file.size > 5 * 1024 * 1024) {
|
||
showToast('❌ PDF zu groß! Max. 5 MB');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
if (file.type !== 'application/pdf') {
|
||
showToast('❌ Nur PDF-Dateien erlaubt!');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (!job.standApproval) {
|
||
job.standApproval = {};
|
||
}
|
||
|
||
job.standApproval.pdf = e.target.result;
|
||
job.standApproval.pdfName = file.name;
|
||
job.standApproval.pdfSize = file.size;
|
||
|
||
document.getElementById('stand-approval-preview').style.display = 'block';
|
||
document.getElementById('stand-approval-name').textContent = file.name;
|
||
document.getElementById('stand-approval-size').textContent = (file.size / 1024).toFixed(1) + ' KB';
|
||
|
||
saveJobs();
|
||
showToast('✅ Standfreigabe hochgeladen');
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
|
||
function downloadStandApproval() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.standApproval || !job.standApproval.pdf) return;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = job.standApproval.pdf;
|
||
link.download = job.standApproval.pdfName || 'standfreigabe.pdf';
|
||
link.click();
|
||
showToast('✅ PDF heruntergeladen');
|
||
}
|
||
|
||
function removeStandApproval() {
|
||
if (!confirm('PDF wirklich entfernen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.standApproval) return;
|
||
|
||
job.standApproval.pdf = null;
|
||
job.standApproval.pdfName = null;
|
||
job.standApproval.pdfSize = null;
|
||
|
||
document.getElementById('stand-approval-preview').style.display = 'none';
|
||
document.getElementById('edit-stand-approval-upload').value = '';
|
||
|
||
saveJobs();
|
||
showToast('✅ PDF entfernt');
|
||
}
|
||
|
||
function downloadStandImage() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.standImage) return;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = job.standImage;
|
||
link.download = job.standImageName || 'messestand.jpg';
|
||
link.click();
|
||
showToast('✅ Bild heruntergeladen');
|
||
}
|
||
|
||
function removeStandImage() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
job.standImage = null;
|
||
job.standImageName = null;
|
||
|
||
// Hide preview
|
||
const preview = document.getElementById('stand-image-preview');
|
||
preview.style.display = 'none';
|
||
|
||
// Clear input
|
||
const input = document.getElementById('stand-image-upload');
|
||
if (input) input.value = '';
|
||
|
||
saveJobs();
|
||
showToast('✅ Bild entfernt');
|
||
console.log('✅ Messestand-Bild entfernt');
|
||
}
|
||
|
||
// Gallery functions
|
||
let currentLightboxIndex = 0;
|
||
|
||
function handleGalleryUpload(event) {
|
||
const files = Array.from(event.target.files);
|
||
if (!files.length) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
// Initialize gallery array if not exists
|
||
if (!job.standGallery) {
|
||
job.standGallery = [];
|
||
}
|
||
|
||
// Check if adding these files would exceed 10 images
|
||
const totalImages = job.standGallery.length + files.length;
|
||
if (totalImages > 10) {
|
||
showToast(`❌ Max. 10 Bilder erlaubt! (${job.standGallery.length}/10 bereits vorhanden)`);
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
let processedCount = 0;
|
||
let errorCount = 0;
|
||
|
||
files.forEach((file, index) => {
|
||
// Check file size (max 2MB)
|
||
if (file.size > 2 * 1024 * 1024) {
|
||
showToast(`❌ "${file.name}" zu groß! Max. 2 MB`);
|
||
errorCount++;
|
||
processedCount++;
|
||
if (processedCount === files.length) {
|
||
finishGalleryUpload(files.length - errorCount);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Check file type
|
||
if (!file.type.startsWith('image/')) {
|
||
showToast(`❌ "${file.name}" ist keine Bilddatei!`);
|
||
errorCount++;
|
||
processedCount++;
|
||
if (processedCount === files.length) {
|
||
finishGalleryUpload(files.length - errorCount);
|
||
}
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
job.standGallery.push({
|
||
id: Date.now().toString(36) + Math.random().toString(36).substring(2),
|
||
name: file.name,
|
||
data: e.target.result,
|
||
uploadedAt: new Date().toISOString()
|
||
});
|
||
|
||
processedCount++;
|
||
if (processedCount === files.length) {
|
||
finishGalleryUpload(files.length - errorCount);
|
||
}
|
||
};
|
||
|
||
reader.onerror = function() {
|
||
showToast(`❌ Fehler beim Hochladen von "${file.name}"`);
|
||
errorCount++;
|
||
processedCount++;
|
||
if (processedCount === files.length) {
|
||
finishGalleryUpload(files.length - errorCount);
|
||
}
|
||
};
|
||
|
||
reader.readAsDataURL(file);
|
||
});
|
||
}
|
||
|
||
function finishGalleryUpload(successCount) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
saveJobs();
|
||
renderGallery();
|
||
|
||
// Clear input
|
||
const input = document.getElementById('gallery-upload-input');
|
||
if (input) input.value = '';
|
||
|
||
if (successCount > 0) {
|
||
showToast(`✅ ${successCount} Bild${successCount > 1 ? 'er' : ''} hochgeladen`);
|
||
console.log(`✅ ${successCount} Standfotos hochgeladen`);
|
||
}
|
||
}
|
||
|
||
function renderGallery() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
const container = document.getElementById('gallery-container');
|
||
|
||
// Initialize gallery array if not exists
|
||
if (!job.standGallery) {
|
||
job.standGallery = [];
|
||
}
|
||
|
||
if (job.standGallery.length === 0) {
|
||
container.innerHTML = `
|
||
<div class="gallery-empty">
|
||
<i class="fas fa-images"></i>
|
||
<p>Noch keine Standfotos hochgeladen</p>
|
||
<p style="font-size: 0.85rem; margin-top: 8px;">Laden Sie Bilder Ihres Messestandes hoch, um eine Galerie zu erstellen.</p>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = `
|
||
<div class="gallery-grid">
|
||
${job.standGallery.map((image, index) => `
|
||
<div class="gallery-item">
|
||
<img src="${image.data}"
|
||
alt="${image.name}"
|
||
class="gallery-item-image"
|
||
onclick="openLightbox(${index})">
|
||
<div class="gallery-item-info">
|
||
<div class="gallery-item-name" title="${image.name}">
|
||
${image.name}
|
||
</div>
|
||
<div class="gallery-item-actions">
|
||
<button class="gallery-item-btn" onclick="downloadGalleryImage('${image.id}')">
|
||
<i class="fas fa-download"></i> Download
|
||
</button>
|
||
<button class="gallery-item-btn delete" onclick="removeGalleryImage('${image.id}')">
|
||
<i class="fas fa-trash"></i> Löschen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function openLightbox(index) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.standGallery || job.standGallery.length === 0) return;
|
||
|
||
currentLightboxIndex = index;
|
||
const lightbox = document.getElementById('lightbox');
|
||
const lightboxImage = document.getElementById('lightbox-image');
|
||
|
||
lightboxImage.src = job.standGallery[index].data;
|
||
lightbox.classList.add('active');
|
||
|
||
// Disable scrolling
|
||
document.body.style.overflow = 'hidden';
|
||
}
|
||
|
||
function closeLightbox() {
|
||
const lightbox = document.getElementById('lightbox');
|
||
lightbox.classList.remove('active');
|
||
|
||
// Enable scrolling
|
||
document.body.style.overflow = 'auto';
|
||
}
|
||
|
||
function navigateLightbox(direction) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.standGallery || job.standGallery.length === 0) return;
|
||
|
||
currentLightboxIndex += direction;
|
||
|
||
// Loop around
|
||
if (currentLightboxIndex < 0) {
|
||
currentLightboxIndex = job.standGallery.length - 1;
|
||
} else if (currentLightboxIndex >= job.standGallery.length) {
|
||
currentLightboxIndex = 0;
|
||
}
|
||
|
||
const lightboxImage = document.getElementById('lightbox-image');
|
||
lightboxImage.src = job.standGallery[currentLightboxIndex].data;
|
||
}
|
||
|
||
function downloadGalleryImage(imageId) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.standGallery) return;
|
||
|
||
const image = job.standGallery.find(img => img.id === imageId);
|
||
if (!image) return;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = image.data;
|
||
link.download = image.name;
|
||
link.click();
|
||
|
||
showToast('✅ Bild heruntergeladen');
|
||
}
|
||
|
||
function removeGalleryImage(imageId) {
|
||
if (!confirm('Bild wirklich löschen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.standGallery) return;
|
||
|
||
job.standGallery = job.standGallery.filter(img => img.id !== imageId);
|
||
|
||
saveJobs();
|
||
renderGallery();
|
||
showToast('✅ Bild gelöscht');
|
||
console.log('✅ Standfoto gelöscht');
|
||
}
|
||
|
||
// Calendar functions
|
||
let currentCalendarDate = new Date();
|
||
|
||
function showDashboardView() {
|
||
document.getElementById('dashboard-view').classList.remove('hidden');
|
||
document.getElementById('calendar-view').classList.add('hidden');
|
||
document.getElementById('job-detail-view').classList.add('hidden');
|
||
|
||
document.getElementById('nav-dashboard').classList.add('active');
|
||
document.getElementById('nav-calendar').classList.remove('active');
|
||
|
||
updateStats();
|
||
renderJobs();
|
||
}
|
||
|
||
function showCalendarView() {
|
||
document.getElementById('dashboard-view').classList.add('hidden');
|
||
document.getElementById('calendar-view').classList.remove('hidden');
|
||
document.getElementById('job-detail-view').classList.add('hidden');
|
||
|
||
document.getElementById('nav-dashboard').classList.remove('active');
|
||
document.getElementById('nav-calendar').classList.add('active');
|
||
|
||
renderCalendar();
|
||
}
|
||
|
||
function changeMonth(delta) {
|
||
currentCalendarDate.setMonth(currentCalendarDate.getMonth() + delta);
|
||
renderCalendar();
|
||
}
|
||
|
||
function goToToday() {
|
||
currentCalendarDate = new Date();
|
||
renderCalendar();
|
||
}
|
||
|
||
function renderCalendar() {
|
||
const container = document.getElementById('calendar-container');
|
||
const monthYearEl = document.getElementById('calendar-month-year');
|
||
|
||
const year = currentCalendarDate.getFullYear();
|
||
const month = currentCalendarDate.getMonth();
|
||
|
||
// Update month/year display
|
||
const monthNames = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
|
||
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
|
||
monthYearEl.textContent = `${monthNames[month]} ${year}`;
|
||
|
||
// Get first and last day of month
|
||
const firstDay = new Date(year, month, 1);
|
||
const lastDay = new Date(year, month + 1, 0);
|
||
const daysInMonth = lastDay.getDate();
|
||
|
||
// Get day of week for first day (0 = Sunday, adjust to Monday = 0)
|
||
let firstDayOfWeek = firstDay.getDay() - 1;
|
||
if (firstDayOfWeek === -1) firstDayOfWeek = 6;
|
||
|
||
// Get events for this month
|
||
const events = getMonthEvents(year, month);
|
||
|
||
// Build calendar HTML
|
||
let html = '<div class="calendar-grid">';
|
||
|
||
// Day headers
|
||
const dayNames = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
|
||
dayNames.forEach(day => {
|
||
html += `<div class="calendar-day-header">${day}</div>`;
|
||
});
|
||
|
||
// Empty cells for days before month starts
|
||
for (let i = 0; i < firstDayOfWeek; i++) {
|
||
const prevMonthDay = new Date(year, month, -(firstDayOfWeek - i - 1));
|
||
html += `<div class="calendar-day other-month">
|
||
<div class="calendar-day-number">${prevMonthDay.getDate()}</div>
|
||
</div>`;
|
||
}
|
||
|
||
// Days of current month
|
||
const today = new Date();
|
||
for (let day = 1; day <= daysInMonth; day++) {
|
||
const date = new Date(year, month, day);
|
||
const dateStr = date.toISOString().split('T')[0];
|
||
const isToday = date.toDateString() === today.toDateString();
|
||
|
||
const dayEvents = events.filter(e => e.date === dateStr);
|
||
|
||
html += `<div class="calendar-day ${isToday ? 'today' : ''}">
|
||
<div class="calendar-day-number">${day}</div>`;
|
||
|
||
dayEvents.forEach(event => {
|
||
if (event.jobIds && event.jobIds.length > 1) {
|
||
// Mehrere Jobs - zeige Auswahl-Dialog
|
||
const jobIdsStr = event.jobIds.join(',');
|
||
html += `<div class="calendar-event calendar-event-multi ${event.type}" data-job-ids="${jobIdsStr}" data-event-name="${event.eventName}" title="${event.title}">
|
||
${event.title}
|
||
</div>`;
|
||
} else {
|
||
// Einzelner Job - direkt öffnen
|
||
html += `<div class="calendar-event calendar-event-single ${event.type}" data-job-id="${event.jobId}" title="${event.title}">
|
||
${event.title}
|
||
</div>`;
|
||
}
|
||
});
|
||
|
||
html += '</div>';
|
||
}
|
||
|
||
// Fill remaining cells
|
||
const totalCells = firstDayOfWeek + daysInMonth;
|
||
const remainingCells = 7 - (totalCells % 7);
|
||
if (remainingCells < 7) {
|
||
for (let i = 1; i <= remainingCells; i++) {
|
||
html += `<div class="calendar-day other-month">
|
||
<div class="calendar-day-number">${i}</div>
|
||
</div>`;
|
||
}
|
||
}
|
||
|
||
html += '</div>';
|
||
container.innerHTML = html;
|
||
|
||
// Event Listener für Kalender-Events
|
||
document.querySelectorAll('.calendar-event-multi').forEach(eventEl => {
|
||
eventEl.addEventListener('click', function() {
|
||
const jobIdsStr = this.getAttribute('data-job-ids');
|
||
const eventName = this.getAttribute('data-event-name');
|
||
showJobSelector(jobIdsStr, eventName);
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('.calendar-event-single').forEach(eventEl => {
|
||
eventEl.addEventListener('click', function() {
|
||
const jobId = this.getAttribute('data-job-id');
|
||
openJob(jobId);
|
||
});
|
||
});
|
||
}
|
||
|
||
// Show job selector for multi-job events
|
||
function showJobSelector(jobIdsStr, eventName) {
|
||
const jobIds = jobIdsStr.split(',');
|
||
const eventJobs = jobs.filter(j => jobIds.includes(j.id));
|
||
|
||
if (eventJobs.length === 0) return;
|
||
|
||
// Erstelle HTML für Job-Auswahl
|
||
let html = `
|
||
<div class="job-selector-modal" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 10000; display: flex; align-items: center; justify-content: center;" onclick="this.remove()">
|
||
<div style="background: white; border-radius: 8px; padding: 30px; max-width: 500px; width: 90%; box-shadow: 0 10px 40px rgba(0,0,0,0.3);" onclick="event.stopPropagation()">
|
||
<h3 style="margin: 0 0 20px 0; font-size: 20px; color: #111111;">
|
||
📅 ${eventName}
|
||
</h3>
|
||
<p style="color: #666; margin-bottom: 20px; font-size: 14px;">
|
||
Wählen Sie einen Stand aus:
|
||
</p>
|
||
<div style="display: flex; flex-direction: column; gap: 10px;">
|
||
`;
|
||
|
||
eventJobs.forEach(job => {
|
||
const customerName = job.customerName || 'Unbenannt';
|
||
const projektNr = job.projektNr || '';
|
||
const standInfo = job.standSize || job.hall || '';
|
||
|
||
html += `
|
||
<button data-job-id="${job.id}" class="job-selector-btn"
|
||
style="
|
||
background: white;
|
||
border: 2px solid #E0E0E0;
|
||
border-radius: 6px;
|
||
padding: 15px;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
font-size: 14px;
|
||
"
|
||
onmouseover="this.style.borderColor='#FFE000'; this.style.background='#FFFBEA';"
|
||
onmouseout="this.style.borderColor='#E0E0E0'; this.style.background='white';">
|
||
<div style="font-weight: 600; color: #111111; margin-bottom: 5px;">
|
||
${customerName}
|
||
</div>
|
||
<div style="font-size: 12px; color: #666;">
|
||
${projektNr ? 'Projekt: ' + projektNr : ''}
|
||
${standInfo ? ' • ' + standInfo : ''}
|
||
</div>
|
||
</button>
|
||
`;
|
||
});
|
||
|
||
html += `
|
||
</div>
|
||
<button onclick="this.parentElement.parentElement.remove()"
|
||
style="
|
||
margin-top: 20px;
|
||
width: 100%;
|
||
padding: 12px;
|
||
background: #F5F5F5;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
color: #666;
|
||
"
|
||
onmouseover="this.style.background='#E0E0E0';"
|
||
onmouseout="this.style.background='#F5F5F5';">
|
||
Abbrechen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.insertAdjacentHTML('beforeend', html);
|
||
|
||
// Event Listener für Job-Auswahl-Buttons
|
||
document.querySelectorAll('.job-selector-btn').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
const jobId = this.getAttribute('data-job-id');
|
||
// Modal entfernen
|
||
this.closest('.job-selector-modal').remove();
|
||
// Job öffnen
|
||
openJob(jobId);
|
||
});
|
||
});
|
||
}
|
||
|
||
// Helper function to get all dates between start and end
|
||
function getDatesBetween(startDate, endDate) {
|
||
const dates = [];
|
||
const current = new Date(startDate);
|
||
const end = new Date(endDate);
|
||
|
||
// Set to start of day to avoid time zone issues
|
||
current.setHours(0, 0, 0, 0);
|
||
end.setHours(0, 0, 0, 0);
|
||
|
||
while (current <= end) {
|
||
dates.push(current.toISOString().split('T')[0]);
|
||
current.setDate(current.getDate() + 1);
|
||
}
|
||
|
||
return dates;
|
||
}
|
||
|
||
function getMonthEvents(year, month) {
|
||
const eventsMap = new Map(); // Für Duplikat-Prüfung
|
||
|
||
jobs.forEach(job => {
|
||
// Setup events (Aufbau)
|
||
if (job.setupStart) {
|
||
const startDate = new Date(job.setupStart);
|
||
const endDate = job.setupEnd ? new Date(job.setupEnd) : startDate;
|
||
const allDates = getDatesBetween(startDate, endDate);
|
||
|
||
allDates.forEach(dateStr => {
|
||
const date = new Date(dateStr);
|
||
if (date.getFullYear() === year && date.getMonth() === month) {
|
||
const key = `${dateStr}-setup-${job.customerName}`;
|
||
if (!eventsMap.has(key)) {
|
||
eventsMap.set(key, {
|
||
date: dateStr,
|
||
title: `🔧 ${job.customerName || 'Aufbau'}`,
|
||
type: 'setup',
|
||
jobId: job.id,
|
||
jobIds: [job.id]
|
||
});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// Event/Messe (Messe/Event) - Gruppierung nach eventName
|
||
if (job.eventStart) {
|
||
const startDate = new Date(job.eventStart);
|
||
const endDate = job.eventEnd ? new Date(job.eventEnd) : startDate;
|
||
const allDates = getDatesBetween(startDate, endDate);
|
||
|
||
allDates.forEach(dateStr => {
|
||
const date = new Date(dateStr);
|
||
if (date.getFullYear() === year && date.getMonth() === month) {
|
||
const eventName = job.eventName || 'Messe';
|
||
const key = `${dateStr}-event-${eventName}`;
|
||
|
||
if (eventsMap.has(key)) {
|
||
// Event existiert bereits - Job hinzufügen
|
||
const existingEvent = eventsMap.get(key);
|
||
existingEvent.jobIds.push(job.id);
|
||
existingEvent.count = existingEvent.jobIds.length;
|
||
existingEvent.title = `📅 ${eventName} (${existingEvent.count} Stände)`;
|
||
} else {
|
||
// Neues Event erstellen
|
||
eventsMap.set(key, {
|
||
date: dateStr,
|
||
title: `📅 ${eventName}`,
|
||
type: 'event',
|
||
jobId: job.id,
|
||
jobIds: [job.id],
|
||
count: 1,
|
||
eventName: eventName
|
||
});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// Teardown events (Abbau)
|
||
if (job.teardownStart) {
|
||
const startDate = new Date(job.teardownStart);
|
||
const endDate = job.teardownEnd ? new Date(job.teardownEnd) : startDate;
|
||
const allDates = getDatesBetween(startDate, endDate);
|
||
|
||
allDates.forEach(dateStr => {
|
||
const date = new Date(dateStr);
|
||
if (date.getFullYear() === year && date.getMonth() === month) {
|
||
const key = `${dateStr}-teardown-${job.customerName}`;
|
||
if (!eventsMap.has(key)) {
|
||
eventsMap.set(key, {
|
||
date: dateStr,
|
||
title: `🔨 ${job.customerName || 'Abbau'}`,
|
||
type: 'teardown',
|
||
jobId: job.id,
|
||
jobIds: [job.id]
|
||
});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
// Deadline events (Bestelldeadlines) - Organisation & Produktion
|
||
jobs.forEach(job => {
|
||
const deadlines = [
|
||
{ date: job.bookingPlatform?.deadline, title: '⏰ Buchungsplattform-Deadline', type: 'deadline', enabled: job.bookingPlatform?.enabled },
|
||
{ date: job.powerBooking?.deadline, title: '⏰ Strom-Deadline', type: 'deadline', enabled: job.powerBooking?.enabled },
|
||
{ date: job.rigging?.deadline, title: '⏰ Rigging-Deadline', type: 'deadline', enabled: job.rigging?.enabled },
|
||
{ date: job.wlan?.deadline, title: '⏰ WLAN-Deadline', type: 'deadline', enabled: job.wlan?.enabled },
|
||
{ date: job.cleaning?.deadline, title: '⏰ Reinigung-Deadline', type: 'deadline', enabled: job.cleaning?.enabled },
|
||
{ date: job.standApproval?.deadline, title: '⏰ Standfreigabe-Deadline', type: 'deadline', enabled: job.standApproval?.enabled },
|
||
{ date: job.druckproduktion?.deadline, title: '⏰ Druckproduktion-Deadline', type: 'deadline', enabled: job.druckproduktion?.enabled }
|
||
];
|
||
|
||
deadlines.forEach(deadline => {
|
||
if (deadline.date && deadline.enabled) {
|
||
const deadlineDate = new Date(deadline.date);
|
||
if (deadlineDate.getFullYear() === year && deadlineDate.getMonth() === month) {
|
||
const dateStr = deadlineDate.toISOString().split('T')[0];
|
||
const key = `${dateStr}-deadline-${deadline.title}-${job.id}`;
|
||
if (!eventsMap.has(key)) {
|
||
eventsMap.set(key, {
|
||
date: dateStr,
|
||
title: `${deadline.title} (${job.customerName || 'Job'})`,
|
||
type: 'deadline',
|
||
jobId: job.id,
|
||
jobIds: [job.id]
|
||
});
|
||
}
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
// Map zurück in Array konvertieren
|
||
return Array.from(eventsMap.values());
|
||
}
|
||
|
||
// Acceptance (Standabnahme) functions
|
||
let signatureCanvas, signatureCtx, isDrawing = false;
|
||
let lastX = 0, lastY = 0;
|
||
|
||
function initSignaturePad() {
|
||
signatureCanvas = document.getElementById('signature-canvas');
|
||
if (!signatureCanvas) return;
|
||
|
||
signatureCtx = signatureCanvas.getContext('2d');
|
||
|
||
// Set actual canvas size
|
||
const rect = signatureCanvas.getBoundingClientRect();
|
||
signatureCanvas.width = 600;
|
||
signatureCanvas.height = 200;
|
||
|
||
// Mouse events
|
||
signatureCanvas.addEventListener('mousedown', startDrawing);
|
||
signatureCanvas.addEventListener('mousemove', draw);
|
||
signatureCanvas.addEventListener('mouseup', stopDrawing);
|
||
signatureCanvas.addEventListener('mouseout', stopDrawing);
|
||
|
||
// Touch events for mobile
|
||
signatureCanvas.addEventListener('touchstart', handleTouchStart, { passive: false });
|
||
signatureCanvas.addEventListener('touchmove', handleTouchMove, { passive: false });
|
||
signatureCanvas.addEventListener('touchend', stopDrawing);
|
||
}
|
||
|
||
function startDrawing(e) {
|
||
isDrawing = true;
|
||
const rect = signatureCanvas.getBoundingClientRect();
|
||
lastX = e.clientX - rect.left;
|
||
lastY = e.clientY - rect.top;
|
||
}
|
||
|
||
function draw(e) {
|
||
if (!isDrawing) return;
|
||
|
||
const rect = signatureCanvas.getBoundingClientRect();
|
||
const x = e.clientX - rect.left;
|
||
const y = e.clientY - rect.top;
|
||
|
||
signatureCtx.beginPath();
|
||
signatureCtx.moveTo(lastX * (signatureCanvas.width / rect.width), lastY * (signatureCanvas.height / rect.height));
|
||
signatureCtx.lineTo(x * (signatureCanvas.width / rect.width), y * (signatureCanvas.height / rect.height));
|
||
signatureCtx.strokeStyle = '#111111';
|
||
signatureCtx.lineWidth = 2;
|
||
signatureCtx.lineCap = 'round';
|
||
signatureCtx.stroke();
|
||
|
||
lastX = x;
|
||
lastY = y;
|
||
}
|
||
|
||
function stopDrawing() {
|
||
isDrawing = false;
|
||
}
|
||
|
||
function handleTouchStart(e) {
|
||
e.preventDefault();
|
||
const touch = e.touches[0];
|
||
const rect = signatureCanvas.getBoundingClientRect();
|
||
isDrawing = true;
|
||
lastX = touch.clientX - rect.left;
|
||
lastY = touch.clientY - rect.top;
|
||
}
|
||
|
||
function handleTouchMove(e) {
|
||
e.preventDefault();
|
||
if (!isDrawing) return;
|
||
|
||
const touch = e.touches[0];
|
||
const rect = signatureCanvas.getBoundingClientRect();
|
||
const x = touch.clientX - rect.left;
|
||
const y = touch.clientY - rect.top;
|
||
|
||
signatureCtx.beginPath();
|
||
signatureCtx.moveTo(lastX * (signatureCanvas.width / rect.width), lastY * (signatureCanvas.height / rect.height));
|
||
signatureCtx.lineTo(x * (signatureCanvas.width / rect.width), y * (signatureCanvas.height / rect.height));
|
||
signatureCtx.strokeStyle = '#111111';
|
||
signatureCtx.lineWidth = 2;
|
||
signatureCtx.lineCap = 'round';
|
||
signatureCtx.stroke();
|
||
|
||
lastX = x;
|
||
lastY = y;
|
||
}
|
||
|
||
function clearSignature() {
|
||
if (!signatureCanvas || !signatureCtx) return;
|
||
signatureCtx.clearRect(0, 0, signatureCanvas.width, signatureCanvas.height);
|
||
document.getElementById('signature-saved-indicator').style.display = 'none';
|
||
}
|
||
|
||
function saveSignature() {
|
||
if (!signatureCanvas) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (!job.acceptance) {
|
||
job.acceptance = {};
|
||
}
|
||
|
||
job.acceptance.signature = signatureCanvas.toDataURL('image/png');
|
||
saveJobs();
|
||
document.getElementById('signature-saved-indicator').style.display = 'block';
|
||
showToast('✅ Unterschrift gespeichert');
|
||
}
|
||
|
||
// Acceptance photos
|
||
function handleAcceptancePhotosUpload(event) {
|
||
const files = Array.from(event.target.files);
|
||
if (!files.length) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (!job.acceptance) {
|
||
job.acceptance = {};
|
||
}
|
||
if (!job.acceptance.photos) {
|
||
job.acceptance.photos = [];
|
||
}
|
||
|
||
if (job.acceptance.photos.length + files.length > 10) {
|
||
showToast('❌ Max. 10 Bilder erlaubt!');
|
||
return;
|
||
}
|
||
|
||
let successCount = 0;
|
||
files.forEach((file, index) => {
|
||
if (file.size > 2 * 1024 * 1024) {
|
||
showToast(`❌ ${file.name} zu groß! Max. 2 MB`);
|
||
return;
|
||
}
|
||
|
||
if (!file.type.startsWith('image/')) {
|
||
showToast(`❌ ${file.name} ist kein Bild!`);
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
job.acceptance.photos.push({
|
||
id: Date.now().toString(36) + Math.random().toString(36).substring(2),
|
||
data: e.target.result,
|
||
name: file.name,
|
||
size: file.size
|
||
});
|
||
|
||
successCount++;
|
||
if (successCount === files.length) {
|
||
renderAcceptanceGallery();
|
||
saveJobs();
|
||
showToast(`✅ ${successCount} Foto(s) hochgeladen`);
|
||
}
|
||
};
|
||
reader.readAsDataURL(file);
|
||
});
|
||
|
||
event.target.value = '';
|
||
}
|
||
|
||
function renderAcceptanceGallery() {
|
||
const container = document.getElementById('acceptance-gallery-container');
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
|
||
if (!job || !job.acceptance || !job.acceptance.photos || job.acceptance.photos.length === 0) {
|
||
container.innerHTML = `
|
||
<div class="gallery-empty">
|
||
<i class="fas fa-camera"></i>
|
||
<p>Noch keine Abnahmefotos hochgeladen</p>
|
||
<p style="font-size: 0.85rem; margin-top: 8px;">Fotografieren Sie den fertigen Stand für die Abnahme.</p>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = `
|
||
<div class="gallery-grid">
|
||
${job.acceptance.photos.map((photo, index) => `
|
||
<div class="gallery-item">
|
||
<img src="${photo.data}" alt="${photo.name}">
|
||
<div class="gallery-item-actions">
|
||
<button class="icon-btn" onclick="downloadAcceptancePhoto(${index})" title="Herunterladen">
|
||
<i class="fas fa-download"></i>
|
||
</button>
|
||
<button class="icon-btn danger" onclick="removeAcceptancePhoto(${index})" title="Löschen">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function downloadAcceptancePhoto(index) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.acceptance || !job.acceptance.photos || !job.acceptance.photos[index]) return;
|
||
|
||
const photo = job.acceptance.photos[index];
|
||
const link = document.createElement('a');
|
||
link.href = photo.data;
|
||
link.download = photo.name || `abnahme-foto-${index + 1}.jpg`;
|
||
link.click();
|
||
showToast('✅ Foto heruntergeladen');
|
||
}
|
||
|
||
function removeAcceptancePhoto(index) {
|
||
if (!confirm('Foto wirklich löschen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.acceptance || !job.acceptance.photos) return;
|
||
|
||
job.acceptance.photos.splice(index, 1);
|
||
renderAcceptanceGallery();
|
||
saveJobs();
|
||
showToast('✅ Foto gelöscht');
|
||
}
|
||
|
||
// Weekly Protocol functions
|
||
function handleWeeklyProtocolUpload(event) {
|
||
const files = Array.from(event.target.files);
|
||
if (files.length === 0) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (!job.weekly) {
|
||
job.weekly = { protocols: [] };
|
||
}
|
||
|
||
let uploadedCount = 0;
|
||
let errorCount = 0;
|
||
|
||
files.forEach(file => {
|
||
// Validate file type
|
||
if (file.type !== 'application/pdf') {
|
||
showToast('❌ Nur PDF-Dateien erlaubt: ' + file.name);
|
||
errorCount++;
|
||
return;
|
||
}
|
||
|
||
// Validate file size (max 10MB)
|
||
if (file.size > 10 * 1024 * 1024) {
|
||
showToast('❌ Datei zu groß (max. 10 MB): ' + file.name);
|
||
errorCount++;
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const protocol = {
|
||
id: Date.now().toString(36) + Math.random().toString(36).substring(2),
|
||
name: file.name,
|
||
size: file.size,
|
||
data: e.target.result,
|
||
uploadDate: new Date().toISOString()
|
||
};
|
||
|
||
job.weekly.protocols.push(protocol);
|
||
uploadedCount++;
|
||
|
||
// Render after last file
|
||
if (uploadedCount + errorCount === files.length) {
|
||
renderWeeklyProtocols();
|
||
saveJobs();
|
||
showToast(`✅ ${uploadedCount} Protokoll(e) hochgeladen`);
|
||
}
|
||
};
|
||
reader.readAsDataURL(file);
|
||
});
|
||
|
||
event.target.value = '';
|
||
}
|
||
|
||
function renderWeeklyProtocols() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.weekly || !job.weekly.protocols || job.weekly.protocols.length === 0) {
|
||
document.getElementById('weekly-protocols-container').innerHTML = `
|
||
<div class="gallery-empty">
|
||
<i class="fas fa-file-pdf"></i>
|
||
<p>Noch keine Protokolle hochgeladen</p>
|
||
<p style="font-size: 0.85rem; margin-top: 8px;">Laden Sie wöchentliche Berichte und Protokolle hoch.</p>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
const html = job.weekly.protocols.map((protocol, index) => `
|
||
<div style="padding: 16px; background: #F5F5F5; border-radius: 8px; border: 2px solid #FFE000; margin-bottom: 12px;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px;">
|
||
<div style="display: flex; align-items: center; gap: 12px; flex: 1; min-width: 200px;">
|
||
<i class="fas fa-file-pdf" style="color: #E74C3C; font-size: 32px;"></i>
|
||
<div style="flex: 1;">
|
||
<div style="font-weight: 600; color: #111111; margin-bottom: 4px;">${protocol.name}</div>
|
||
<div style="font-size: 13px; color: #666;">
|
||
${(protocol.size / 1024).toFixed(1)} KB •
|
||
${new Date(protocol.uploadDate).toLocaleDateString('de-DE', {
|
||
day: '2-digit',
|
||
month: '2-digit',
|
||
year: 'numeric',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
})}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<button class="btn btn-secondary btn-small" onclick="downloadWeeklyProtocol(${index})" title="Herunterladen">
|
||
<i class="fas fa-download"></i>
|
||
</button>
|
||
<button class="btn btn-secondary btn-small" onclick="removeWeeklyProtocol(${index})" title="Löschen">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
|
||
document.getElementById('weekly-protocols-container').innerHTML = html;
|
||
}
|
||
|
||
function downloadWeeklyProtocol(index) {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.weekly || !job.weekly.protocols || !job.weekly.protocols[index]) return;
|
||
|
||
const protocol = job.weekly.protocols[index];
|
||
const link = document.createElement('a');
|
||
link.href = protocol.data;
|
||
link.download = protocol.name;
|
||
link.click();
|
||
showToast('✅ Protokoll heruntergeladen');
|
||
}
|
||
|
||
function removeWeeklyProtocol(index) {
|
||
if (!confirm('Protokoll wirklich löschen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.weekly || !job.weekly.protocols) return;
|
||
|
||
const protocolName = job.weekly.protocols[index].name;
|
||
job.weekly.protocols.splice(index, 1);
|
||
renderWeeklyProtocols();
|
||
saveJobs();
|
||
showToast(`✅ ${protocolName} gelöscht`);
|
||
}
|
||
|
||
// Briefing document upload functions
|
||
|
||
// 1. Briefing Customer
|
||
function handleBriefingCustomerUpload(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
// Check file size (max 10MB)
|
||
if (file.size > 10 * 1024 * 1024) {
|
||
showToast('❌ Datei zu groß! Max. 10 MB');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
if (!job.briefing) {
|
||
job.briefing = {};
|
||
}
|
||
|
||
job.briefing.customerDoc = e.target.result;
|
||
job.briefing.customerDocName = file.name;
|
||
job.briefing.customerDocSize = file.size;
|
||
|
||
// Show preview
|
||
document.getElementById('briefing-customer-preview').style.display = 'block';
|
||
document.getElementById('briefing-customer-name').textContent = file.name;
|
||
document.getElementById('briefing-customer-size').textContent = (file.size / 1024).toFixed(1) + ' KB';
|
||
|
||
saveJobs();
|
||
showToast('✅ Briefing Kunde hochgeladen');
|
||
console.log('✅ Briefing Kunde hochgeladen:', file.name);
|
||
};
|
||
reader.onerror = function() {
|
||
showToast('❌ Fehler beim Lesen der Datei');
|
||
console.error('❌ FileReader Fehler');
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
|
||
function downloadBriefingCustomer() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.briefing || !job.briefing.customerDoc) return;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = job.briefing.customerDoc;
|
||
link.download = job.briefing.customerDocName || 'briefing-kunde.pdf';
|
||
link.click();
|
||
showToast('✅ Briefing heruntergeladen');
|
||
}
|
||
|
||
function removeBriefingCustomer() {
|
||
if (!confirm('Briefing Kunde wirklich entfernen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.briefing) return;
|
||
|
||
job.briefing.customerDoc = null;
|
||
job.briefing.customerDocName = null;
|
||
job.briefing.customerDocSize = null;
|
||
|
||
document.getElementById('briefing-customer-preview').style.display = 'none';
|
||
document.getElementById('briefing-customer-upload').value = '';
|
||
|
||
saveJobs();
|
||
showToast('✅ Briefing entfernt');
|
||
}
|
||
|
||
// 2. Brandbook / Logo
|
||
function handleBriefingBrandUpload(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (file.size > 10 * 1024 * 1024) {
|
||
showToast('❌ Datei zu groß! Max. 10 MB');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
if (!job.briefing) {
|
||
job.briefing = {};
|
||
}
|
||
|
||
job.briefing.brandDoc = e.target.result;
|
||
job.briefing.brandDocName = file.name;
|
||
job.briefing.brandDocSize = file.size;
|
||
|
||
document.getElementById('briefing-brand-preview').style.display = 'block';
|
||
document.getElementById('briefing-brand-name').textContent = file.name;
|
||
document.getElementById('briefing-brand-size').textContent = (file.size / 1024).toFixed(1) + ' KB';
|
||
|
||
saveJobs();
|
||
showToast('✅ Brandbook hochgeladen');
|
||
console.log('✅ Brandbook hochgeladen:', file.name);
|
||
};
|
||
reader.onerror = function() {
|
||
showToast('❌ Fehler beim Lesen der Datei');
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
|
||
function downloadBriefingBrand() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.briefing || !job.briefing.brandDoc) return;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = job.briefing.brandDoc;
|
||
link.download = job.briefing.brandDocName || 'brandbook.pdf';
|
||
link.click();
|
||
showToast('✅ Brandbook heruntergeladen');
|
||
}
|
||
|
||
function removeBriefingBrand() {
|
||
if (!confirm('Brandbook wirklich entfernen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.briefing) return;
|
||
|
||
job.briefing.brandDoc = null;
|
||
job.briefing.brandDocName = null;
|
||
job.briefing.brandDocSize = null;
|
||
|
||
document.getElementById('briefing-brand-preview').style.display = 'none';
|
||
document.getElementById('briefing-brand-upload').value = '';
|
||
|
||
saveJobs();
|
||
showToast('✅ Brandbook entfernt');
|
||
}
|
||
|
||
// 3. Standplan
|
||
function handleBriefingPlanUpload(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (file.size > 10 * 1024 * 1024) {
|
||
showToast('❌ Datei zu groß! Max. 10 MB');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
if (!job.briefing) {
|
||
job.briefing = {};
|
||
}
|
||
|
||
job.briefing.planDoc = e.target.result;
|
||
job.briefing.planDocName = file.name;
|
||
job.briefing.planDocSize = file.size;
|
||
|
||
document.getElementById('briefing-plan-preview').style.display = 'block';
|
||
document.getElementById('briefing-plan-name').textContent = file.name;
|
||
document.getElementById('briefing-plan-size').textContent = (file.size / 1024).toFixed(1) + ' KB';
|
||
|
||
saveJobs();
|
||
showToast('✅ Standplan hochgeladen');
|
||
console.log('✅ Standplan hochgeladen:', file.name);
|
||
};
|
||
reader.onerror = function() {
|
||
showToast('❌ Fehler beim Lesen der Datei');
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
|
||
function downloadBriefingPlan() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.briefing || !job.briefing.planDoc) return;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = job.briefing.planDoc;
|
||
link.download = job.briefing.planDocName || 'standplan.pdf';
|
||
link.click();
|
||
showToast('✅ Standplan heruntergeladen');
|
||
}
|
||
|
||
function removeBriefingPlan() {
|
||
if (!confirm('Standplan wirklich entfernen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.briefing) return;
|
||
|
||
job.briefing.planDoc = null;
|
||
job.briefing.planDocName = null;
|
||
job.briefing.planDocSize = null;
|
||
|
||
document.getElementById('briefing-plan-preview').style.display = 'none';
|
||
document.getElementById('briefing-plan-upload').value = '';
|
||
|
||
saveJobs();
|
||
showToast('✅ Standplan entfernt');
|
||
}
|
||
|
||
// 4. Weitere Dokumente
|
||
function handleBriefingAdditionalUpload(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job) return;
|
||
|
||
if (file.size > 10 * 1024 * 1024) {
|
||
showToast('❌ Datei zu groß! Max. 10 MB');
|
||
event.target.value = '';
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
if (!job.briefing) {
|
||
job.briefing = {};
|
||
}
|
||
|
||
job.briefing.additionalDoc = e.target.result;
|
||
job.briefing.additionalDocName = file.name;
|
||
job.briefing.additionalDocSize = file.size;
|
||
|
||
document.getElementById('briefing-additional-preview').style.display = 'block';
|
||
document.getElementById('briefing-additional-name').textContent = file.name;
|
||
document.getElementById('briefing-additional-size').textContent = (file.size / 1024).toFixed(1) + ' KB';
|
||
|
||
saveJobs();
|
||
showToast('✅ Dokument hochgeladen');
|
||
console.log('✅ Weiteres Dokument hochgeladen:', file.name);
|
||
};
|
||
reader.onerror = function() {
|
||
showToast('❌ Fehler beim Lesen der Datei');
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
|
||
function downloadBriefingAdditional() {
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.briefing || !job.briefing.additionalDoc) return;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = job.briefing.additionalDoc;
|
||
link.download = job.briefing.additionalDocName || 'dokument.pdf';
|
||
link.click();
|
||
showToast('✅ Dokument heruntergeladen');
|
||
}
|
||
|
||
function removeBriefingAdditional() {
|
||
if (!confirm('Dokument wirklich entfernen?')) return;
|
||
|
||
const job = jobs.find(j => j.id === currentJobId);
|
||
if (!job || !job.briefing) return;
|
||
|
||
job.briefing.additionalDoc = null;
|
||
job.briefing.additionalDocName = null;
|
||
job.briefing.additionalDocSize = null;
|
||
|
||
document.getElementById('briefing-additional-preview').style.display = 'none';
|
||
document.getElementById('briefing-additional-upload').value = '';
|
||
|
||
saveJobs();
|
||
showToast('✅ Dokument entfernt');
|
||
}
|
||
|
||
// Open Google Maps with location
|
||
function openGoogleMaps() {
|
||
const location = document.getElementById('edit-location').value;
|
||
|
||
if (!location || location.trim() === '') {
|
||
showToast('❌ Bitte geben Sie einen Ort ein');
|
||
return;
|
||
}
|
||
|
||
// Encode location for URL
|
||
const encodedLocation = encodeURIComponent(location.trim());
|
||
|
||
// Open Google Maps in new tab
|
||
const mapsUrl = `https://www.google.com/maps/search/?api=1&query=${encodedLocation}`;
|
||
window.open(mapsUrl, '_blank');
|
||
|
||
showToast('✅ Google Maps geöffnet');
|
||
}
|
||
|
||
// Show toast notification
|
||
function showToast(message) {
|
||
const toast = document.getElementById('toast');
|
||
const toastMessage = document.getElementById('toast-message');
|
||
|
||
toastMessage.textContent = message;
|
||
toast.classList.add('show');
|
||
|
||
setTimeout(() => {
|
||
toast.classList.remove('show');
|
||
}, 3000);
|
||
}
|
||
|
||
// Test-Funktion die wir direkt aufrufen können
|
||
window.testButton = function() {
|
||
alert('Button funktioniert!');
|
||
console.log('✅ Button Test erfolgreich!');
|
||
};
|
||
</script>
|
||
</body>
|
||
</html>
|