Multimedix commited on
Commit
dbaba6b
·
verified ·
1 Parent(s): c3ef34d

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +681 -750
index.html CHANGED
@@ -3,14 +3,8 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Haushaltsbuch Pro - Ausgaben & Einnahmen Tracker</title>
7
-
8
- <!-- Chart.js -->
9
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
10
-
11
- <!-- Lucide Icons -->
12
- <script src="https://unpkg.com/lucide@latest"></script>
13
-
14
  <style>
15
  * {
16
  margin: 0;
@@ -29,12 +23,12 @@
29
  --light: #f8fafc;
30
  --gray: #64748b;
31
  --border: #e2e8f0;
32
- --shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
33
- --shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
34
  }
35
 
36
  body {
37
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
38
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
39
  min-height: 100vh;
40
  color: var(--dark);
@@ -48,12 +42,12 @@
48
 
49
  header {
50
  background: rgba(255, 255, 255, 0.98);
 
51
  border-radius: 20px;
52
- padding: 25px 30px;
53
  margin-bottom: 30px;
54
  box-shadow: var(--shadow-lg);
55
- backdrop-filter: blur(10px);
56
- animation: slideDown 0.5s ease-out;
57
  }
58
 
59
  .header-content {
@@ -68,189 +62,95 @@
68
  display: flex;
69
  align-items: center;
70
  gap: 15px;
 
 
 
71
  }
72
 
73
  .logo i {
74
- width: 45px;
75
- height: 45px;
76
  background: linear-gradient(135deg, var(--primary), var(--secondary));
77
- border-radius: 12px;
78
- display: flex;
79
- align-items: center;
80
- justify-content: center;
81
- color: white;
82
- }
83
-
84
- .logo h1 {
85
- font-size: 28px;
86
- background: linear-gradient(135deg, var(--primary), var(--primary-dark));
87
  -webkit-background-clip: text;
88
  -webkit-text-fill-color: transparent;
89
  }
90
 
91
- .header-actions {
92
  display: flex;
93
- gap: 12px;
94
  flex-wrap: wrap;
95
  }
96
 
97
- .btn {
98
- padding: 10px 20px;
99
- border: none;
100
- border-radius: 10px;
101
- font-size: 14px;
102
- font-weight: 600;
103
- cursor: pointer;
104
- transition: all 0.3s ease;
105
- display: inline-flex;
106
- align-items: center;
107
- gap: 8px;
108
- white-space: nowrap;
109
- }
110
-
111
- .btn-primary {
112
- background: linear-gradient(135deg, var(--primary), var(--primary-dark));
113
- color: white;
114
- }
115
-
116
- .btn-primary:hover {
117
- transform: translateY(-2px);
118
- box-shadow: 0 10px 20px -5px rgba(99, 102, 241, 0.5);
119
  }
120
 
121
- .btn-secondary {
122
- background: white;
 
123
  color: var(--dark);
124
- border: 2px solid var(--border);
125
- }
126
-
127
- .btn-secondary:hover {
128
- background: var(--light);
129
- border-color: var(--primary);
130
- color: var(--primary);
131
- }
132
-
133
- .btn-success {
134
- background: var(--success);
135
- color: white;
136
- }
137
-
138
- .btn-danger {
139
- background: var(--danger);
140
- color: white;
141
- }
142
-
143
- .dashboard {
144
- display: grid;
145
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
146
- gap: 20px;
147
- margin-bottom: 30px;
148
- }
149
-
150
- .stat-card {
151
- background: rgba(255, 255, 255, 0.98);
152
- padding: 25px;
153
- border-radius: 15px;
154
- box-shadow: var(--shadow);
155
- transition: all 0.3s ease;
156
- animation: fadeInUp 0.5s ease-out;
157
- position: relative;
158
- overflow: hidden;
159
- }
160
-
161
- .stat-card::before {
162
- content: '';
163
- position: absolute;
164
- top: 0;
165
- left: 0;
166
- width: 4px;
167
- height: 100%;
168
- background: linear-gradient(135deg, var(--primary), var(--secondary));
169
- }
170
-
171
- .stat-card:hover {
172
- transform: translateY(-5px);
173
- box-shadow: var(--shadow-lg);
174
- }
175
-
176
- .stat-card.income::before {
177
- background: var(--success);
178
- }
179
-
180
- .stat-card.expense::before {
181
- background: var(--danger);
182
- }
183
-
184
- .stat-card.balance::before {
185
- background: var(--warning);
186
  }
187
 
188
  .stat-label {
189
- font-size: 13px;
190
  color: var(--gray);
191
  text-transform: uppercase;
192
  letter-spacing: 1px;
193
- margin-bottom: 8px;
194
- display: flex;
195
- align-items: center;
196
- gap: 8px;
197
  }
198
 
199
- .stat-value {
200
- font-size: 28px;
201
- font-weight: 700;
202
- color: var(--dark);
203
- }
204
-
205
- .main-content {
206
  display: grid;
207
- grid-template-columns: 1fr 1fr;
208
- gap: 30px;
209
  margin-bottom: 30px;
210
  }
211
 
212
- @media (max-width: 968px) {
213
- .main-content {
214
- grid-template-columns: 1fr;
215
- }
216
- }
217
-
218
  .card {
219
  background: rgba(255, 255, 255, 0.98);
 
220
  border-radius: 20px;
221
- padding: 30px;
222
  box-shadow: var(--shadow-lg);
223
- animation: fadeInUp 0.6s ease-out;
 
 
 
 
 
 
224
  }
225
 
226
  .card-header {
227
  display: flex;
228
  justify-content: space-between;
229
  align-items: center;
230
- margin-bottom: 25px;
231
  padding-bottom: 15px;
232
  border-bottom: 2px solid var(--border);
233
  }
234
 
235
  .card-title {
236
  font-size: 20px;
237
- font-weight: 700;
238
  color: var(--dark);
239
  display: flex;
240
  align-items: center;
241
  gap: 10px;
242
  }
243
 
 
 
 
 
244
  .form-group {
245
  margin-bottom: 20px;
246
  }
247
 
248
  label {
249
  display: block;
250
- font-size: 14px;
251
- font-weight: 600;
252
- color: var(--dark);
253
  margin-bottom: 8px;
 
 
 
254
  }
255
 
256
  input, select, textarea {
@@ -258,7 +158,7 @@
258
  padding: 12px 15px;
259
  border: 2px solid var(--border);
260
  border-radius: 10px;
261
- font-size: 14px;
262
  transition: all 0.3s ease;
263
  background: white;
264
  }
@@ -269,33 +169,55 @@
269
  box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
270
  }
271
 
272
- .input-group {
273
- display: flex;
274
- gap: 10px;
 
 
 
 
 
 
 
 
275
  }
276
 
277
- .input-group input {
278
- flex: 1;
 
279
  }
280
 
281
- .transaction-list {
282
- max-height: 400px;
283
- overflow-y: auto;
284
- padding-right: 10px;
285
  }
286
 
287
- .transaction-list::-webkit-scrollbar {
288
- width: 6px;
 
289
  }
290
 
291
- .transaction-list::-webkit-scrollbar-track {
 
 
 
 
 
292
  background: var(--light);
293
- border-radius: 10px;
 
294
  }
295
 
296
- .transaction-list::-webkit-scrollbar-thumb {
297
- background: var(--gray);
298
- border-radius: 10px;
 
 
 
 
 
 
 
299
  }
300
 
301
  .transaction-item {
@@ -303,17 +225,24 @@
303
  justify-content: space-between;
304
  align-items: center;
305
  padding: 15px;
306
- margin-bottom: 12px;
307
  background: var(--light);
308
- border-radius: 12px;
309
  transition: all 0.3s ease;
310
- animation: slideIn 0.3s ease-out;
311
  }
312
 
313
  .transaction-item:hover {
314
- background: white;
315
- box-shadow: 0 5px 15px -3px rgba(0, 0, 0, 0.1);
316
  transform: translateX(5px);
 
 
 
 
 
 
 
 
 
317
  }
318
 
319
  .transaction-info {
@@ -321,48 +250,22 @@
321
  }
322
 
323
  .transaction-category {
324
- display: inline-block;
325
- padding: 4px 10px;
326
- background: var(--primary);
327
- color: white;
328
- border-radius: 20px;
329
- font-size: 11px;
330
- font-weight: 600;
331
- text-transform: uppercase;
332
- margin-right: 10px;
333
- }
334
-
335
- .transaction-date {
336
  font-size: 12px;
337
  color: var(--gray);
338
- margin-top: 5px;
339
  }
340
 
341
  .transaction-amount {
342
- font-size: 18px;
343
  font-weight: 700;
344
- margin-right: 15px;
345
  }
346
 
347
- .transaction-amount.income {
348
  color: var(--success);
349
  }
350
 
351
- .transaction-amount.expense {
352
- color: var(--danger);
353
- }
354
-
355
- .delete-btn {
356
- background: none;
357
- border: none;
358
  color: var(--danger);
359
- cursor: pointer;
360
- padding: 5px;
361
- transition: all 0.3s ease;
362
- }
363
-
364
- .delete-btn:hover {
365
- transform: scale(1.2);
366
  }
367
 
368
  .chart-container {
@@ -371,46 +274,18 @@
371
  margin-top: 20px;
372
  }
373
 
374
- .tabs {
375
- display: flex;
376
- gap: 10px;
377
- margin-bottom: 20px;
378
- border-bottom: 2px solid var(--border);
379
  }
380
 
381
- .tab {
382
- padding: 12px 20px;
383
- background: none;
384
- border: none;
385
- font-size: 14px;
386
  font-weight: 600;
387
- color: var(--gray);
388
- cursor: pointer;
389
- transition: all 0.3s ease;
390
- position: relative;
391
- }
392
-
393
- .tab.active {
394
- color: var(--primary);
395
- }
396
-
397
- .tab.active::after {
398
- content: '';
399
- position: absolute;
400
- bottom: -2px;
401
- left: 0;
402
- right: 0;
403
- height: 2px;
404
- background: var(--primary);
405
- }
406
-
407
- .tab-content {
408
- display: none;
409
- }
410
-
411
- .tab-content.active {
412
- display: block;
413
- animation: fadeIn 0.3s ease-out;
414
  }
415
 
416
  .modal {
@@ -423,79 +298,24 @@
423
  background: rgba(0, 0, 0, 0.5);
424
  backdrop-filter: blur(5px);
425
  z-index: 1000;
426
- animation: fadeIn 0.3s ease-out;
427
  }
428
 
429
  .modal.active {
430
  display: flex;
431
- align-items: center;
432
  justify-content: center;
 
433
  }
434
 
435
  .modal-content {
436
  background: white;
437
- padding: 30px;
438
  border-radius: 20px;
 
439
  max-width: 500px;
440
  width: 90%;
441
- max-height: 80vh;
442
  overflow-y: auto;
443
- animation: slideUp 0.3s ease-out;
444
- }
445
-
446
- .modal-header {
447
- display: flex;
448
- justify-content: space-between;
449
- align-items: center;
450
- margin-bottom: 20px;
451
- }
452
-
453
- .modal-title {
454
- font-size: 24px;
455
- font-weight: 700;
456
- color: var(--dark);
457
- }
458
-
459
- .close-modal {
460
- background: none;
461
- border: none;
462
- font-size: 24px;
463
- color: var(--gray);
464
- cursor: pointer;
465
- transition: all 0.3s ease;
466
- }
467
-
468
- .close-modal:hover {
469
- color: var(--danger);
470
- transform: rotate(90deg);
471
- }
472
-
473
- .file-input-wrapper {
474
- position: relative;
475
- overflow: hidden;
476
- display: inline-block;
477
- width: 100%;
478
- }
479
-
480
- .file-input-wrapper input[type=file] {
481
- position: absolute;
482
- left: -9999px;
483
- }
484
-
485
- .file-input-label {
486
- display: block;
487
- padding: 12px 20px;
488
- background: var(--light);
489
- border: 2px dashed var(--border);
490
- border-radius: 10px;
491
- text-align: center;
492
- cursor: pointer;
493
- transition: all 0.3s ease;
494
- }
495
-
496
- .file-input-label:hover {
497
- background: white;
498
- border-color: var(--primary);
499
  }
500
 
501
  .toast {
@@ -509,7 +329,7 @@
509
  display: none;
510
  align-items: center;
511
  gap: 10px;
512
- animation: slideInRight 0.3s ease-out;
513
  z-index: 2000;
514
  }
515
 
@@ -525,19 +345,49 @@
525
  border-left: 4px solid var(--danger);
526
  }
527
 
528
- .toast.info {
529
- border-left: 4px solid var(--primary);
 
 
530
  }
531
 
532
- @keyframes slideDown {
533
- from {
534
- opacity: 0;
535
- transform: translateY(-20px);
536
- }
537
- to {
538
- opacity: 1;
539
- transform: translateY(0);
540
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
  }
542
 
543
  @keyframes fadeInUp {
@@ -551,14 +401,14 @@
551
  }
552
  }
553
 
554
- @keyframes slideIn {
555
  from {
556
  opacity: 0;
557
- transform: translateX(-20px);
558
  }
559
  to {
560
  opacity: 1;
561
- transform: translateX(0);
562
  }
563
  }
564
 
@@ -574,120 +424,166 @@
574
  }
575
 
576
  @keyframes slideInRight {
577
- from {
578
- transform: translateX(100%);
579
- }
580
- to {
581
- transform: translateX(0);
582
- }
583
- }
584
-
585
- @keyframes fadeIn {
586
  from {
587
  opacity: 0;
 
588
  }
589
  to {
590
  opacity: 1;
 
591
  }
592
  }
593
 
594
- .footer {
595
- text-align: center;
596
- padding: 20px;
597
- color: white;
598
- font-size: 14px;
599
- }
600
-
601
- .footer a {
602
- color: var(--secondary);
603
- text-decoration: none;
604
- font-weight: 600;
605
- transition: all 0.3s ease;
606
- }
607
-
608
- .footer a:hover {
609
- text-decoration: underline;
610
- }
611
-
612
  @media (max-width: 768px) {
613
  .container {
614
  padding: 10px;
615
  }
616
 
 
 
 
 
617
  .header-content {
618
  flex-direction: column;
619
  text-align: center;
620
  }
621
 
622
- .dashboard {
623
- grid-template-columns: 1fr;
624
  }
625
 
626
- .card {
627
- padding: 20px;
628
  }
629
 
630
- .modal-content {
631
- padding: 20px;
 
632
  }
633
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
634
  </style>
635
  </head>
636
  <body>
637
  <div class="container">
638
  <header>
 
 
 
639
  <div class="header-content">
640
  <div class="logo">
641
- <i data-lucide="wallet"></i>
642
- <h1>Haushaltsbuch Pro</h1>
643
  </div>
644
- <div class="header-actions">
645
- <button class="btn btn-secondary" onclick="openImportModal()">
646
- <i data-lucide="upload" style="width: 16px; height: 16px;"></i>
647
- CSV Import
648
- </button>
649
- <button class="btn btn-secondary" onclick="exportCSV()">
650
- <i data-lucide="download" style="width: 16px; height: 16px;"></i>
651
- CSV Export
652
- </button>
653
- <button class="btn btn-danger" onclick="clearAllData()">
654
- <i data-lucide="trash-2" style="width: 16px; height: 16px;"></i>
655
- Alle Daten löschen
656
- </button>
657
  </div>
658
  </div>
659
  </header>
660
 
661
- <div class="dashboard">
662
- <div class="stat-card income">
663
- <div class="stat-label">
664
- <i data-lucide="trending-up" style="width: 16px; height: 16px;"></i>
665
- Gesamteinnahmen
 
 
 
 
 
 
666
  </div>
667
- <div class="stat-value" id="totalIncome">€0,00</div>
 
668
  </div>
669
- <div class="stat-card expense">
670
- <div class="stat-label">
671
- <i data-lucide="trending-down" style="width: 16px; height: 16px;"></i>
672
- Gesamtausgaben
673
  </div>
674
- <div class="stat-value" id="totalExpense">€0,00</div>
 
675
  </div>
676
- <div class="stat-card balance">
677
- <div class="stat-label">
678
- <i data-lucide="balance" style="width: 16px; height: 16px;"></i>
679
- Kontostand
680
  </div>
681
- <div class="stat-value" id="balance">€0,00</div>
 
682
  </div>
683
  </div>
684
 
685
- <div class="main-content">
686
  <div class="card">
687
  <div class="card-header">
688
  <h2 class="card-title">
689
- <i data-lucide="plus-circle"></i>
690
- Neue Transaktion
691
  </h2>
692
  </div>
693
  <form id="transactionForm">
@@ -701,7 +597,7 @@
701
  </div>
702
  <div class="form-group">
703
  <label for="amount">Betrag (€)</label>
704
- <input type="number" id="amount" step="0.01" min="0" placeholder="0,00" required>
705
  </div>
706
  <div class="form-group">
707
  <label for="category">Kategorie</label>
@@ -709,17 +605,17 @@
709
  <option value="">Bitte wählen</option>
710
  </select>
711
  </div>
712
- <div class="form-group">
713
- <label for="description">Beschreibung</label>
714
- <input type="text" id="description" placeholder="z.B. Lebensmittel, Gehalt, Miete" required>
715
- </div>
716
  <div class="form-group">
717
  <label for="date">Datum</label>
718
  <input type="date" id="date" required>
719
  </div>
 
 
 
 
720
  <button type="submit" class="btn btn-primary" style="width: 100%;">
721
- <i data-lucide="save" style="width: 16px; height: 16px;"></i>
722
- Transaktion speichern
723
  </button>
724
  </form>
725
  </div>
@@ -727,126 +623,117 @@
727
  <div class="card">
728
  <div class="card-header">
729
  <h2 class="card-title">
730
- <i data-lucide="list"></i>
731
- Letzte Transaktionen
732
  </h2>
733
- <button class="btn btn-secondary" onclick="refreshTransactions()">
734
- <i data-lucide="refresh-cw" style="width: 16px; height: 16px;"></i>
735
  </button>
736
  </div>
 
 
 
 
 
737
  <div class="transaction-list" id="transactionList">
738
- <!-- Transactions will be added here -->
 
 
 
739
  </div>
740
  </div>
741
  </div>
742
 
743
- <div class="card">
744
- <div class="card-header">
745
- <h2 class="card-title">
746
- <i data-lucide="bar-chart-2"></i>
747
- Auswertungen
748
- </h2>
749
- </div>
750
- <div class="tabs">
751
- <button class="tab active" onclick="switchTab('monthly')">Monatlich</button>
752
- <button class="tab" onclick="switchTab('category')">Nach Kategorie</button>
753
- <button class="tab" onclick="switchTab('trend')">Trend</button>
754
- </div>
755
- <div class="tab-content active" id="monthly-tab">
756
- <div class="chart-container">
757
- <canvas id="monthlyChart"></canvas>
758
  </div>
759
- </div>
760
- <div class="tab-content" id="category-tab">
761
  <div class="chart-container">
762
  <canvas id="categoryChart"></canvas>
763
  </div>
764
  </div>
765
- <div class="tab-content" id="trend-tab">
 
 
 
 
 
 
 
766
  <div class="chart-container">
767
- <canvas id="trendChart"></canvas>
768
  </div>
769
  </div>
770
  </div>
771
 
772
- <footer class="footer">
773
- <p>© 2024 Haushaltsbuch Pro | Gebaut mit <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a></p>
774
- </footer>
775
- </div>
776
-
777
- <!-- Import Modal -->
778
- <div class="modal" id="importModal">
779
- <div class="modal-content">
780
- <div class="modal-header">
781
- <h3 class="modal-title">CSV Import</h3>
782
- <button class="close-modal" onclick="closeImportModal()">×</button>
783
  </div>
784
- <div class="form-group">
785
- <label>Wählen Sie eine CSV-Datei aus</label>
786
- <div class="file-input-wrapper">
787
- <input type="file" id="csvFile" accept=".csv" onchange="handleFileSelect(event)">
788
- <label for="csvFile" class="file-input-label">
789
- <i data-lucide="file-text" style="width: 24px; height: 24px; margin-bottom: 10px;"></i>
790
- <div>Klicken Sie hier oder ziehen Sie eine Datei hierher</div>
791
- <div style="font-size: 12px; color: var(--gray); margin-top: 5px;">
792
- Format: Typ,Betrag,Kategorie,Beschreibung,Datum
793
- </div>
794
- </label>
795
- </div>
 
 
796
  </div>
797
- <button class="btn btn-primary" onclick="importCSV()" style="width: 100%; margin-top: 20px;">
798
- Importieren
799
- </button>
800
  </div>
801
  </div>
802
 
803
- <!-- Toast Notification -->
804
  <div class="toast" id="toast">
805
- <i data-lucide="check-circle" style="width: 20px; height: 20px;"></i>
806
- <span id="toastMessage"></span>
807
  </div>
808
 
809
  <script>
810
- // Initialize Lucide icons
811
- lucide.createIcons();
812
-
813
- // Data Management
814
  let transactions = JSON.parse(localStorage.getItem('transactions')) || [];
815
- let monthlyChart, categoryChart, trendChart;
816
 
817
- // Categories
818
- const incomeCategories = ['Gehalt', 'Nebeneinkommen', 'Investitionen', 'Geschenk', 'Rückerstattung', 'Sonstige'];
819
- const expenseCategories = ['Lebensmittel', 'Miete', 'Transport', 'Unterhaltung', 'Rechnungen', 'Gesundheit', 'Kleidung', 'Bildung', 'Restaurants', 'Shopping', 'Sonstige'];
 
820
 
821
- // Initialize
822
- document.addEventListener('DOMContentLoaded', function() {
823
- // Set today's date
824
- document.getElementById('date').valueAsDate = new Date();
825
-
826
- // Update categories based on type
827
- document.getElementById('type').addEventListener('change', updateCategories);
828
-
829
- // Form submission
830
- document.getElementById('transactionForm').addEventListener('submit', addTransaction);
831
-
832
- // Initial load
833
- updateDashboard();
834
- displayTransactions();
835
- initializeCharts();
836
  });
837
 
838
- function updateCategories() {
839
- const type = document.getElementById('type').value;
840
  const categorySelect = document.getElementById('category');
841
  categorySelect.innerHTML = '<option value="">Bitte wählen</option>';
842
 
843
- const categories = type === 'income' ? incomeCategories : expenseCategories;
844
- categories.forEach(cat => {
845
- categorySelect.innerHTML += `<option value="${cat}">${cat}</option>`;
846
- });
 
 
 
 
847
  }
848
 
849
- function addTransaction(e) {
 
850
  e.preventDefault();
851
 
852
  const transaction = {
@@ -854,364 +741,408 @@
854
  type: document.getElementById('type').value,
855
  amount: parseFloat(document.getElementById('amount').value),
856
  category: document.getElementById('category').value,
 
857
  description: document.getElementById('description').value,
858
- date: document.getElementById('date').value
859
  };
860
 
861
- transactions.unshift(transaction);
862
  saveTransactions();
 
863
 
864
- // Reset form
865
- document.getElementById('transactionForm').reset();
866
  document.getElementById('date').valueAsDate = new Date();
867
 
868
- // Update UI
869
- updateDashboard();
870
- displayTransactions();
871
- updateCharts();
872
-
873
- showToast('Transaktion erfolgreich hinzugefügt!', 'success');
874
- }
875
-
876
- function deleteTransaction(id) {
877
- if (confirm('Möchten Sie diese Transaktion wirklich löschen?')) {
878
- transactions = transactions.filter(t => t.id !== id);
879
- saveTransactions();
880
- updateDashboard();
881
- displayTransactions();
882
- updateCharts();
883
- showToast('Transaktion gelöscht', 'info');
884
- }
885
- }
886
 
887
  function saveTransactions() {
888
  localStorage.setItem('transactions', JSON.stringify(transactions));
889
  }
890
 
891
- function updateDashboard() {
892
- const income = transactions
893
- .filter(t => t.type === 'income')
894
- .reduce((sum, t) => sum + t.amount, 0);
 
 
 
 
 
 
 
895
 
896
- const expense = transactions
897
- .filter(t => t.type === 'expense')
898
- .reduce((sum, t) => sum + t.amount, 0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
899
 
900
- const balance = income - expense;
901
 
902
- document.getElementById('totalIncome').textContent = formatCurrency(income);
903
- document.getElementById('totalExpense').textContent = formatCurrency(expense);
904
- document.getElementById('balance').textContent = formatCurrency(balance);
 
 
 
 
 
 
 
 
 
905
 
906
- // Update balance color
907
- const balanceElement = document.getElementById('balance');
908
- if (balance < 0) {
909
- balanceElement.style.color = 'var(--danger)';
910
- } else if (balance > 0) {
911
- balanceElement.style.color = 'var(--success)';
912
  }
913
- }
914
-
915
- function displayTransactions() {
916
- const listElement = document.getElementById('transactionList');
917
- const recentTransactions = transactions.slice(0, 10);
918
 
919
- if (recentTransactions.length === 0) {
920
- listElement.innerHTML = '<div style="text-align: center; color: var(--gray); padding: 40px;">Keine Transaktionen vorhanden</div>';
 
 
 
 
 
 
 
 
921
  return;
922
  }
923
 
924
- listElement.innerHTML = recentTransactions.map(t => `
925
- <div class="transaction-item">
926
  <div class="transaction-info">
927
- <span class="transaction-category" style="background: ${t.type === 'income' ? 'var(--success)' : 'var(--danger)'}">
928
- ${t.category}
929
- </span>
930
- <div style="font-weight: 600; margin-top: 5px;">${t.description}</div>
931
- <div class="transaction-date">${formatDate(t.date)}</div>
932
  </div>
933
- <div class="transaction-amount ${t.type}">
934
  ${t.type === 'income' ? '+' : '-'}${formatCurrency(t.amount)}
935
  </div>
936
- <button class="delete-btn" onclick="deleteTransaction(${t.id})">
937
- <i data-lucide="trash-2" style="width: 18px; height: 18px;"></i>
938
  </button>
939
  </div>
940
  `).join('');
 
 
 
 
 
 
 
 
 
 
 
 
 
941
 
942
- // Re-initialize icons
943
- lucide.createIcons();
944
- }
945
-
946
- function initializeCharts() {
947
- // Monthly Chart
948
- const monthlyCtx = document.getElementById('monthlyChart').getContext('2d');
949
- monthlyChart = new Chart(monthlyCtx, {
950
- type: 'bar',
951
- data: {
952
- labels: [],
953
- datasets: [{
954
- label: 'Einnahmen',
955
- data: [],
956
- backgroundColor: 'rgba(16, 185, 129, 0.8)',
957
- borderRadius: 8
958
- }, {
959
- label: 'Ausgaben',
960
- data: [],
961
- backgroundColor: 'rgba(239, 68, 68, 0.8)',
962
- borderRadius: 8
963
- }]
964
- },
965
- options: {
966
- responsive: true,
967
- maintainAspectRatio: false,
968
- plugins: {
969
- legend: {
970
- position: 'top',
971
- }
972
- },
973
- scales: {
974
- y: {
975
- beginAtZero: true,
976
- ticks: {
977
- callback: function(value) {
978
- return '€' + value.toLocaleString('de-DE');
979
- }
980
- }
981
- }
982
- }
983
- }
984
  });
 
 
 
 
985
 
986
- // Category Chart
987
- const categoryCtx = document.getElementById('categoryChart').getContext('2d');
988
- categoryChart = new Chart(categoryCtx, {
989
- type: 'doughnut',
990
- data: {
991
- labels: [],
992
- datasets: [{
993
- data: [],
994
- backgroundColor: [
995
- '#6366f1', '#8b5cf6', '#ec4899', '#f43f5e',
996
- '#ef4444', '#f97316', '#f59e0b', '#eab308',
997
- '#84cc16', '#22c55e', '#10b981', '#14b8a6'
998
- ],
999
- borderWidth: 0
1000
- }]
1001
- },
1002
- options: {
1003
- responsive: true,
1004
- maintainAspectRatio: false,
1005
- plugins: {
1006
- legend: {
1007
- position: 'right',
1008
- }
1009
- }
1010
- }
1011
- });
1012
 
1013
- // Trend Chart
1014
- const trendCtx = document.getElementById('trendChart').getContext('2d');
1015
- trendChart = new Chart(trendCtx, {
1016
- type: 'line',
1017
- data: {
1018
- labels: [],
1019
- datasets: [{
1020
- label: 'Kontostand',
1021
- data: [],
1022
- borderColor: '#6366f1',
1023
- backgroundColor: 'rgba(99, 102, 241, 0.1)',
1024
- tension: 0.4,
1025
- fill: true
1026
- }]
1027
- },
1028
- options: {
1029
- responsive: true,
1030
- maintainAspectRatio: false,
1031
- plugins: {
1032
- legend: {
1033
- display: false
1034
- }
1035
- },
1036
- scales: {
1037
- y: {
1038
- beginAtZero: false,
1039
- ticks: {
1040
- callback: function(value) {
1041
- return '€' + value.toLocaleString('de-DE');
1042
- }
1043
- }
1044
- }
1045
- }
1046
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1047
  });
1048
-
1049
- updateCharts();
1050
  }
1051
 
1052
- function updateCharts() {
1053
- // Update Monthly Chart
1054
- const monthlyData = getMonthlyData();
1055
- monthlyChart.data.labels = monthlyData.labels;
1056
- monthlyChart.data.datasets[0].data = monthlyData.income;
1057
- monthlyChart.data.datasets[1].data = monthlyData.expense;
1058
- monthlyChart.update();
1059
-
1060
- // Update Category Chart
1061
- const categoryData = getCategoryData();
1062
- categoryChart.data.labels = categoryData.labels;
1063
- categoryChart.data.datasets[0].data = categoryData.data;
1064
- categoryChart.update();
1065
-
1066
- // Update Trend Chart
1067
- const trendData = getTrendData();
1068
- trendChart.data.labels = trendData.labels;
1069
- trendChart.data.datasets[0].data = trendData.balance;
1070
- trendChart.update();
1071
- }
1072
-
1073
- function getMonthlyData() {
1074
- const last6Months = [];
1075
  const incomeData = [];
1076
  const expenseData = [];
1077
 
1078
  for (let i = 5; i >= 0; i--) {
1079
  const date = new Date();
1080
  date.setMonth(date.getMonth() - i);
1081
- const monthKey = date.toISOString().slice(0, 7);
1082
 
1083
- const monthTransactions = transactions.filter(t => t.date.startsWith(monthKey));
1084
- const income = monthTransactions
1085
- .filter(t => t.type === 'income')
 
1086
  .reduce((sum, t) => sum + t.amount, 0);
1087
- const expense = monthTransactions
1088
- .filter(t => t.type === 'expense')
 
1089
  .reduce((sum, t) => sum + t.amount, 0);
1090
 
1091
- const monthName = date.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' });
1092
- last6Months.push(monthName);
1093
- incomeData.push(income);
1094
- expenseData.push(expense);
1095
  }
1096
 
1097
- return {
1098
- labels: last6Months,
1099
- income: incomeData,
1100
- expense: expenseData
1101
- };
1102
- }
1103
-
1104
- function getCategoryData() {
1105
- const categoryTotals = {};
1106
 
1107
- transactions
1108
- .filter(t => t.type === 'expense')
1109
- .forEach(t => {
1110
- categoryTotals[t.category] = (categoryTotals[t.category] || 0) + t.amount;
1111
- });
1112
 
1113
- return {
1114
- labels: Object.keys(categoryTotals),
1115
- data: Object.values(categoryTotals)
1116
- };
1117
- }
1118
-
1119
- function getTrendData() {
1120
- const sortedTransactions = [...transactions].sort((a, b) => new Date(a.date) - new Date(b.date));
1121
- const labels = [];
1122
- const balanceData = [];
1123
- let runningBalance = 0;
1124
 
1125
- sortedTransactions.forEach(t => {
1126
- if (t.type === 'income') {
1127
- runningBalance += t.amount;
1128
- } else {
1129
- runningBalance -= t.amount;
1130
- }
 
 
1131
 
1132
- labels.push(formatDate(t.date));
1133
- balanceData.push(runningBalance);
 
 
 
 
 
 
 
 
1134
  });
1135
 
1136
- return {
1137
- labels: labels.slice(-30), // Last 30 transactions
1138
- balance: balanceData.slice(-30)
1139
- };
1140
- }
1141
-
1142
- function switchTab(tabName) {
1143
- // Update tabs
1144
- document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
1145
- event.target.classList.add('active');
1146
 
1147
- // Update content
1148
- document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
1149
- document.getElementById(`${tabName}-tab`).classList.add('active');
1150
- }
1151
-
1152
- function refreshTransactions() {
1153
- displayTransactions();
1154
- showToast('Transaktionen aktualisiert', 'info');
1155
- }
1156
-
1157
- function openImportModal() {
1158
- document.getElementById('importModal').classList.add('active');
1159
- }
1160
-
1161
- function closeImportModal() {
1162
- document.getElementById('importModal').classList.remove('active');
1163
  }
1164
 
1165
- function handleFileSelect(event) {
1166
- const file = event.target.files[0];
1167
- if (file) {
1168
- const fileName = file.name;
1169
- document.querySelector('.file-input-label div').textContent = fileName;
1170
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
1171
  }
1172
 
1173
- function importCSV() {
1174
- const fileInput = document.getElementById('csvFile');
1175
- const file = fileInput.files[0];
1176
-
1177
- if (!file) {
1178
- showToast('Bitte wählen Sie eine Datei aus', 'error');
1179
- return;
1180
- }
1181
 
1182
  const reader = new FileReader();
1183
  reader.onload = function(e) {
1184
  try {
1185
  const lines = e.target.result.split('\n');
1186
- const newTransactions = [];
1187
 
1188
- lines.forEach((line, index) => {
1189
- if (index === 0 || !line.trim()) return; // Skip header or empty lines
1190
 
1191
- const parts = line.split(',');
1192
- if (parts.length >= 5) {
1193
  const transaction = {
1194
- id: Date.now() + index,
1195
- type: parts[0].trim(),
1196
- amount: parseFloat(parts[1].trim()),
1197
  category: parts[2].trim(),
1198
- description: parts[3].trim(),
1199
- date: parts[4].trim()
 
1200
  };
1201
-
1202
- if (transaction.type && !isNaN(transaction.amount) && transaction.category && transaction.description && transaction.date) {
1203
- newTransactions.push(transaction);
1204
- }
1205
  }
1206
- });
1207
 
1208
- if (newTransactions.length > 0) {
1209
- transactions = [...newTransactions, ...transactions];
1210
  saveTransactions();
1211
- updateDashboard();
1212
- displayTransactions();
1213
- updateCharts();
1214
- closeImportModal();
1215
- showToast(`${newTransactions.length} Transaktionen importiert`, 'success');
1216
  } else {
1217
- showToast('Keine gültigen Transaktionen in der Datei
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Haushaltsbuch Pro - Ausgaben-Tracker</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
 
 
 
 
 
 
8
  <style>
9
  * {
10
  margin: 0;
 
23
  --light: #f8fafc;
24
  --gray: #64748b;
25
  --border: #e2e8f0;
26
+ --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
27
+ --shadow-lg: 0 10px 25px -5px rgb(0 0 0 / 0.1);
28
  }
29
 
30
  body {
31
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
32
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
33
  min-height: 100vh;
34
  color: var(--dark);
 
42
 
43
  header {
44
  background: rgba(255, 255, 255, 0.98);
45
+ backdrop-filter: blur(10px);
46
  border-radius: 20px;
47
+ padding: 25px;
48
  margin-bottom: 30px;
49
  box-shadow: var(--shadow-lg);
50
+ animation: slideDown 0.5s ease;
 
51
  }
52
 
53
  .header-content {
 
62
  display: flex;
63
  align-items: center;
64
  gap: 15px;
65
+ font-size: 28px;
66
+ font-weight: 700;
67
+ color: var(--primary);
68
  }
69
 
70
  .logo i {
71
+ font-size: 36px;
 
72
  background: linear-gradient(135deg, var(--primary), var(--secondary));
 
 
 
 
 
 
 
 
 
 
73
  -webkit-background-clip: text;
74
  -webkit-text-fill-color: transparent;
75
  }
76
 
77
+ .header-stats {
78
  display: flex;
79
+ gap: 30px;
80
  flex-wrap: wrap;
81
  }
82
 
83
+ .stat-item {
84
+ text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  }
86
 
87
+ .stat-value {
88
+ font-size: 24px;
89
+ font-weight: 700;
90
  color: var(--dark);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
92
 
93
  .stat-label {
94
+ font-size: 12px;
95
  color: var(--gray);
96
  text-transform: uppercase;
97
  letter-spacing: 1px;
 
 
 
 
98
  }
99
 
100
+ .grid-container {
 
 
 
 
 
 
101
  display: grid;
102
+ grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
103
+ gap: 25px;
104
  margin-bottom: 30px;
105
  }
106
 
 
 
 
 
 
 
107
  .card {
108
  background: rgba(255, 255, 255, 0.98);
109
+ backdrop-filter: blur(10px);
110
  border-radius: 20px;
111
+ padding: 25px;
112
  box-shadow: var(--shadow-lg);
113
+ animation: fadeInUp 0.5s ease;
114
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
115
+ }
116
+
117
+ .card:hover {
118
+ transform: translateY(-5px);
119
+ box-shadow: 0 20px 40px -10px rgb(0 0 0 / 0.2);
120
  }
121
 
122
  .card-header {
123
  display: flex;
124
  justify-content: space-between;
125
  align-items: center;
126
+ margin-bottom: 20px;
127
  padding-bottom: 15px;
128
  border-bottom: 2px solid var(--border);
129
  }
130
 
131
  .card-title {
132
  font-size: 20px;
133
+ font-weight: 600;
134
  color: var(--dark);
135
  display: flex;
136
  align-items: center;
137
  gap: 10px;
138
  }
139
 
140
+ .card-title i {
141
+ color: var(--primary);
142
+ }
143
+
144
  .form-group {
145
  margin-bottom: 20px;
146
  }
147
 
148
  label {
149
  display: block;
 
 
 
150
  margin-bottom: 8px;
151
+ font-weight: 500;
152
+ color: var(--dark);
153
+ font-size: 14px;
154
  }
155
 
156
  input, select, textarea {
 
158
  padding: 12px 15px;
159
  border: 2px solid var(--border);
160
  border-radius: 10px;
161
+ font-size: 15px;
162
  transition: all 0.3s ease;
163
  background: white;
164
  }
 
169
  box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
170
  }
171
 
172
+ .btn {
173
+ padding: 12px 24px;
174
+ border: none;
175
+ border-radius: 10px;
176
+ font-size: 15px;
177
+ font-weight: 600;
178
+ cursor: pointer;
179
+ transition: all 0.3s ease;
180
+ display: inline-flex;
181
+ align-items: center;
182
+ gap: 8px;
183
  }
184
 
185
+ .btn-primary {
186
+ background: linear-gradient(135deg, var(--primary), var(--primary-dark));
187
+ color: white;
188
  }
189
 
190
+ .btn-primary:hover {
191
+ transform: translateY(-2px);
192
+ box-shadow: 0 10px 20px -5px rgba(99, 102, 241, 0.4);
 
193
  }
194
 
195
+ .btn-success {
196
+ background: linear-gradient(135deg, var(--success), #059669);
197
+ color: white;
198
  }
199
 
200
+ .btn-danger {
201
+ background: linear-gradient(135deg, var(--danger), #dc2626);
202
+ color: white;
203
+ }
204
+
205
+ .btn-secondary {
206
  background: var(--light);
207
+ color: var(--dark);
208
+ border: 2px solid var(--border);
209
  }
210
 
211
+ .btn-group {
212
+ display: flex;
213
+ gap: 10px;
214
+ flex-wrap: wrap;
215
+ }
216
+
217
+ .transaction-list {
218
+ max-height: 400px;
219
+ overflow-y: auto;
220
+ padding-right: 10px;
221
  }
222
 
223
  .transaction-item {
 
225
  justify-content: space-between;
226
  align-items: center;
227
  padding: 15px;
228
+ margin-bottom: 10px;
229
  background: var(--light);
230
+ border-radius: 10px;
231
  transition: all 0.3s ease;
232
+ border-left: 4px solid transparent;
233
  }
234
 
235
  .transaction-item:hover {
 
 
236
  transform: translateX(5px);
237
+ box-shadow: var(--shadow);
238
+ }
239
+
240
+ .transaction-item.income {
241
+ border-left-color: var(--success);
242
+ }
243
+
244
+ .transaction-item.expense {
245
+ border-left-color: var(--danger);
246
  }
247
 
248
  .transaction-info {
 
250
  }
251
 
252
  .transaction-category {
 
 
 
 
 
 
 
 
 
 
 
 
253
  font-size: 12px;
254
  color: var(--gray);
255
+ margin-top: 4px;
256
  }
257
 
258
  .transaction-amount {
 
259
  font-weight: 700;
260
+ font-size: 18px;
261
  }
262
 
263
+ .amount-income {
264
  color: var(--success);
265
  }
266
 
267
+ .amount-expense {
 
 
 
 
 
 
268
  color: var(--danger);
 
 
 
 
 
 
 
269
  }
270
 
271
  .chart-container {
 
274
  margin-top: 20px;
275
  }
276
 
277
+ canvas {
278
+ max-width: 100%;
279
+ height: auto !important;
 
 
280
  }
281
 
282
+ .category-badge {
283
+ display: inline-block;
284
+ padding: 4px 12px;
285
+ border-radius: 20px;
286
+ font-size: 12px;
287
  font-weight: 600;
288
+ margin-right: 8px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  }
290
 
291
  .modal {
 
298
  background: rgba(0, 0, 0, 0.5);
299
  backdrop-filter: blur(5px);
300
  z-index: 1000;
301
+ animation: fadeIn 0.3s ease;
302
  }
303
 
304
  .modal.active {
305
  display: flex;
 
306
  justify-content: center;
307
+ align-items: center;
308
  }
309
 
310
  .modal-content {
311
  background: white;
 
312
  border-radius: 20px;
313
+ padding: 30px;
314
  max-width: 500px;
315
  width: 90%;
316
+ max-height: 90vh;
317
  overflow-y: auto;
318
+ animation: slideUp 0.3s ease;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  }
320
 
321
  .toast {
 
329
  display: none;
330
  align-items: center;
331
  gap: 10px;
332
+ animation: slideInRight 0.3s ease;
333
  z-index: 2000;
334
  }
335
 
 
345
  border-left: 4px solid var(--danger);
346
  }
347
 
348
+ .empty-state {
349
+ text-align: center;
350
+ padding: 40px;
351
+ color: var(--gray);
352
  }
353
 
354
+ .empty-state i {
355
+ font-size: 48px;
356
+ margin-bottom: 15px;
357
+ opacity: 0.5;
358
+ }
359
+
360
+ .filter-tabs {
361
+ display: flex;
362
+ gap: 10px;
363
+ margin-bottom: 20px;
364
+ flex-wrap: wrap;
365
+ }
366
+
367
+ .tab {
368
+ padding: 8px 16px;
369
+ background: var(--light);
370
+ border: 2px solid var(--border);
371
+ border-radius: 20px;
372
+ cursor: pointer;
373
+ transition: all 0.3s ease;
374
+ font-size: 14px;
375
+ font-weight: 500;
376
+ }
377
+
378
+ .tab.active {
379
+ background: var(--primary);
380
+ color: white;
381
+ border-color: var(--primary);
382
+ }
383
+
384
+ .tab:hover:not(.active) {
385
+ background: var(--border);
386
+ }
387
+
388
+ @keyframes fadeIn {
389
+ from { opacity: 0; }
390
+ to { opacity: 1; }
391
  }
392
 
393
  @keyframes fadeInUp {
 
401
  }
402
  }
403
 
404
+ @keyframes slideDown {
405
  from {
406
  opacity: 0;
407
+ transform: translateY(-20px);
408
  }
409
  to {
410
  opacity: 1;
411
+ transform: translateY(0);
412
  }
413
  }
414
 
 
424
  }
425
 
426
  @keyframes slideInRight {
 
 
 
 
 
 
 
 
 
427
  from {
428
  opacity: 0;
429
+ transform: translateX(20px);
430
  }
431
  to {
432
  opacity: 1;
433
+ transform: translateX(0);
434
  }
435
  }
436
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  @media (max-width: 768px) {
438
  .container {
439
  padding: 10px;
440
  }
441
 
442
+ .grid-container {
443
+ grid-template-columns: 1fr;
444
+ }
445
+
446
  .header-content {
447
  flex-direction: column;
448
  text-align: center;
449
  }
450
 
451
+ .header-stats {
452
+ justify-content: center;
453
  }
454
 
455
+ .btn-group {
456
+ flex-direction: column;
457
  }
458
 
459
+ .btn {
460
+ width: 100%;
461
+ justify-content: center;
462
  }
463
  }
464
+
465
+ .summary-cards {
466
+ display: grid;
467
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
468
+ gap: 15px;
469
+ margin-bottom: 25px;
470
+ }
471
+
472
+ .summary-card {
473
+ background: linear-gradient(135deg, var(--light), white);
474
+ padding: 20px;
475
+ border-radius: 15px;
476
+ text-align: center;
477
+ border: 2px solid var(--border);
478
+ transition: all 0.3s ease;
479
+ }
480
+
481
+ .summary-card:hover {
482
+ transform: translateY(-3px);
483
+ box-shadow: var(--shadow);
484
+ }
485
+
486
+ .summary-card-icon {
487
+ font-size: 24px;
488
+ margin-bottom: 10px;
489
+ }
490
+
491
+ .summary-card-value {
492
+ font-size: 24px;
493
+ font-weight: 700;
494
+ margin-bottom: 5px;
495
+ }
496
+
497
+ .summary-card-label {
498
+ font-size: 12px;
499
+ color: var(--gray);
500
+ text-transform: uppercase;
501
+ }
502
+
503
+ .built-with {
504
+ position: absolute;
505
+ top: 10px;
506
+ right: 20px;
507
+ font-size: 12px;
508
+ color: var(--gray);
509
+ }
510
+
511
+ .built-with a {
512
+ color: var(--primary);
513
+ text-decoration: none;
514
+ font-weight: 600;
515
+ }
516
+
517
+ .built-with a:hover {
518
+ text-decoration: underline;
519
+ }
520
  </style>
521
  </head>
522
  <body>
523
  <div class="container">
524
  <header>
525
+ <div class="built-with">
526
+ Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a>
527
+ </div>
528
  <div class="header-content">
529
  <div class="logo">
530
+ <i class="fas fa-wallet"></i>
531
+ <span>Haushaltsbuch Pro</span>
532
  </div>
533
+ <div class="header-stats">
534
+ <div class="stat-item">
535
+ <div class="stat-value" id="totalBalance">0,00 €</div>
536
+ <div class="stat-label">Gesamtguthaben</div>
537
+ </div>
538
+ <div class="stat-item">
539
+ <div class="stat-value" id="monthIncome">0,00 €</div>
540
+ <div class="stat-label">Monatseinnahmen</div>
541
+ </div>
542
+ <div class="stat-item">
543
+ <div class="stat-value" id="monthExpense">0,00 €</div>
544
+ <div class="stat-label">Monatsausgaben</div>
545
+ </div>
546
  </div>
547
  </div>
548
  </header>
549
 
550
+ <div class="summary-cards">
551
+ <div class="summary-card">
552
+ <div class="summary-card-icon" style="color: var(--success);">
553
+ <i class="fas fa-arrow-trend-up"></i>
554
+ </div>
555
+ <div class="summary-card-value" style="color: var(--success);" id="todayIncome">0,00 €</div>
556
+ <div class="summary-card-label">Heutige Einnahmen</div>
557
+ </div>
558
+ <div class="summary-card">
559
+ <div class="summary-card-icon" style="color: var(--danger);">
560
+ <i class="fas fa-arrow-trend-down"></i>
561
  </div>
562
+ <div class="summary-card-value" style="color: var(--danger);" id="todayExpense">0,00 €</div>
563
+ <div class="summary-card-label">Heutige Ausgaben</div>
564
  </div>
565
+ <div class="summary-card">
566
+ <div class="summary-card-icon" style="color: var(--primary);">
567
+ <i class="fas fa-chart-pie"></i>
 
568
  </div>
569
+ <div class="summary-card-value" style="color: var(--primary);" id="categoryCount">0</div>
570
+ <div class="summary-card-label">Kategorien</div>
571
  </div>
572
+ <div class="summary-card">
573
+ <div class="summary-card-icon" style="color: var(--warning);">
574
+ <i class="fas fa-receipt"></i>
 
575
  </div>
576
+ <div class="summary-card-value" style="color: var(--warning);" id="transactionCount">0</div>
577
+ <div class="summary-card-label">Transaktionen</div>
578
  </div>
579
  </div>
580
 
581
+ <div class="grid-container">
582
  <div class="card">
583
  <div class="card-header">
584
  <h2 class="card-title">
585
+ <i class="fas fa-plus-circle"></i>
586
+ Neue Buchung
587
  </h2>
588
  </div>
589
  <form id="transactionForm">
 
597
  </div>
598
  <div class="form-group">
599
  <label for="amount">Betrag (€)</label>
600
+ <input type="number" id="amount" step="0.01" min="0" required placeholder="0,00">
601
  </div>
602
  <div class="form-group">
603
  <label for="category">Kategorie</label>
 
605
  <option value="">Bitte wählen</option>
606
  </select>
607
  </div>
 
 
 
 
608
  <div class="form-group">
609
  <label for="date">Datum</label>
610
  <input type="date" id="date" required>
611
  </div>
612
+ <div class="form-group">
613
+ <label for="description">Beschreibung</label>
614
+ <textarea id="description" rows="2" placeholder="Optionale Beschreibung..."></textarea>
615
+ </div>
616
  <button type="submit" class="btn btn-primary" style="width: 100%;">
617
+ <i class="fas fa-save"></i>
618
+ Buchung speichern
619
  </button>
620
  </form>
621
  </div>
 
623
  <div class="card">
624
  <div class="card-header">
625
  <h2 class="card-title">
626
+ <i class="fas fa-history"></i>
627
+ Letzte Buchungen
628
  </h2>
629
+ <button class="btn btn-secondary" onclick="clearAllTransactions()">
630
+ <i class="fas fa-trash"></i>
631
  </button>
632
  </div>
633
+ <div class="filter-tabs">
634
+ <div class="tab active" onclick="filterTransactions('all')">Alle</div>
635
+ <div class="tab" onclick="filterTransactions('income')">Einnahmen</div>
636
+ <div class="tab" onclick="filterTransactions('expense')">Ausgaben</div>
637
+ </div>
638
  <div class="transaction-list" id="transactionList">
639
+ <div class="empty-state">
640
+ <i class="fas fa-inbox"></i>
641
+ <p>Keine Buchungen vorhanden</p>
642
+ </div>
643
  </div>
644
  </div>
645
  </div>
646
 
647
+ <div class="grid-container">
648
+ <div class="card">
649
+ <div class="card-header">
650
+ <h2 class="card-title">
651
+ <i class="fas fa-chart-line"></i>
652
+ Ausgaben nach Kategorien
653
+ </h2>
 
 
 
 
 
 
 
 
654
  </div>
 
 
655
  <div class="chart-container">
656
  <canvas id="categoryChart"></canvas>
657
  </div>
658
  </div>
659
+
660
+ <div class="card">
661
+ <div class="card-header">
662
+ <h2 class="card-title">
663
+ <i class="fas fa-calendar-alt"></i>
664
+ Monatlicher Verlauf
665
+ </h2>
666
+ </div>
667
  <div class="chart-container">
668
+ <canvas id="monthlyChart"></canvas>
669
  </div>
670
  </div>
671
  </div>
672
 
673
+ <div class="card">
674
+ <div class="card-header">
675
+ <h2 class="card-title">
676
+ <i class="fas fa-cog"></i>
677
+ Datenverwaltung
678
+ </h2>
 
 
 
 
 
679
  </div>
680
+ <div class="btn-group">
681
+ <button class="btn btn-success" onclick="exportToCSV()">
682
+ <i class="fas fa-download"></i>
683
+ CSV exportieren
684
+ </button>
685
+ <label class="btn btn-primary">
686
+ <i class="fas fa-upload"></i>
687
+ CSV importieren
688
+ <input type="file" accept=".csv" style="display: none;" onchange="importFromCSV(event)">
689
+ </label>
690
+ <button class="btn btn-danger" onclick="clearAllData()">
691
+ <i class="fas fa-trash-alt"></i>
692
+ Alle Daten löschen
693
+ </button>
694
  </div>
 
 
 
695
  </div>
696
  </div>
697
 
 
698
  <div class="toast" id="toast">
699
+ <i class="fas fa-check-circle"></i>
700
+ <span id="toastMessage">Erfolgreich gespeichert!</span>
701
  </div>
702
 
703
  <script>
704
+ // Daten initialisieren
 
 
 
705
  let transactions = JSON.parse(localStorage.getItem('transactions')) || [];
706
+ let currentFilter = 'all';
707
 
708
+ const categories = {
709
+ income: ['Gehalt', 'Nebenjob', 'Investment', 'Geschenk', 'Sonstige Einnahmen'],
710
+ expense: ['Lebensmittel', 'Miete', 'Transport', 'Utilities', 'Unterhaltung', 'Gesundheit', 'Shopping', 'Bildung', 'Restaurant', 'Sonstige Ausgaben']
711
+ };
712
 
713
+ // Datum auf heute setzen
714
+ document.getElementById('date').valueAsDate = new Date();
715
+
716
+ // Typ-Änderung behandeln
717
+ document.getElementById('type').addEventListener('change', function() {
718
+ updateCategoryOptions(this.value);
 
 
 
 
 
 
 
 
 
719
  });
720
 
721
+ function updateCategoryOptions(type) {
 
722
  const categorySelect = document.getElementById('category');
723
  categorySelect.innerHTML = '<option value="">Bitte wählen</option>';
724
 
725
+ if (type && categories[type]) {
726
+ categories[type].forEach(cat => {
727
+ const option = document.createElement('option');
728
+ option.value = cat;
729
+ option.textContent = cat;
730
+ categorySelect.appendChild(option);
731
+ });
732
+ }
733
  }
734
 
735
+ // Formular absenden
736
+ document.getElementById('transactionForm').addEventListener('submit', function(e) {
737
  e.preventDefault();
738
 
739
  const transaction = {
 
741
  type: document.getElementById('type').value,
742
  amount: parseFloat(document.getElementById('amount').value),
743
  category: document.getElementById('category').value,
744
+ date: document.getElementById('date').value,
745
  description: document.getElementById('description').value,
746
+ timestamp: new Date().toISOString()
747
  };
748
 
749
+ transactions.push(transaction);
750
  saveTransactions();
751
+ updateUI();
752
 
753
+ // Formular zurücksetzen
754
+ this.reset();
755
  document.getElementById('date').valueAsDate = new Date();
756
 
757
+ showToast('Buchung erfolgreich gespeichert!', 'success');
758
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
759
 
760
  function saveTransactions() {
761
  localStorage.setItem('transactions', JSON.stringify(transactions));
762
  }
763
 
764
+ function updateUI() {
765
+ updateStats();
766
+ updateTransactionList();
767
+ updateCharts();
768
+ }
769
+
770
+ function updateStats() {
771
+ const now = new Date();
772
+ const currentMonth = now.getMonth();
773
+ const currentYear = now.getFullYear();
774
+ const today = now.toISOString().split('T')[0];
775
 
776
+ let totalIncome = 0;
777
+ let totalExpense = 0;
778
+ let monthIncome = 0;
779
+ let monthExpense = 0;
780
+ let todayIncome = 0;
781
+ let todayExpense = 0;
782
+ let uniqueCategories = new Set();
783
+
784
+ transactions.forEach(t => {
785
+ const tDate = new Date(t.date);
786
+ const tMonth = tDate.getMonth();
787
+ const tYear = tDate.getFullYear();
788
+
789
+ uniqueCategories.add(t.category);
790
+
791
+ if (t.type === 'income') {
792
+ totalIncome += t.amount;
793
+ if (tMonth === currentMonth && tYear === currentYear) {
794
+ monthIncome += t.amount;
795
+ }
796
+ if (t.date === today) {
797
+ todayIncome += t.amount;
798
+ }
799
+ } else {
800
+ totalExpense += t.amount;
801
+ if (tMonth === currentMonth && tYear === currentYear) {
802
+ monthExpense += t.amount;
803
+ }
804
+ if (t.date === today) {
805
+ todayExpense += t.amount;
806
+ }
807
+ }
808
+ });
809
 
810
+ const balance = totalIncome - totalExpense;
811
 
812
+ document.getElementById('totalBalance').textContent = formatCurrency(balance);
813
+ document.getElementById('monthIncome').textContent = formatCurrency(monthIncome);
814
+ document.getElementById('monthExpense').textContent = formatCurrency(monthExpense);
815
+ document.getElementById('todayIncome').textContent = formatCurrency(todayIncome);
816
+ document.getElementById('todayExpense').textContent = formatCurrency(todayExpense);
817
+ document.getElementById('categoryCount').textContent = uniqueCategories.size;
818
+ document.getElementById('transactionCount').textContent = transactions.length;
819
+ }
820
+
821
+ function updateTransactionList() {
822
+ const list = document.getElementById('transactionList');
823
+ let filteredTransactions = transactions;
824
 
825
+ if (currentFilter !== 'all') {
826
+ filteredTransactions = transactions.filter(t => t.type === currentFilter);
 
 
 
 
827
  }
 
 
 
 
 
828
 
829
+ // Sortieren nach Datum (neueste zuerst)
830
+ filteredTransactions.sort((a, b) => new Date(b.date) - new Date(a.date));
831
+
832
+ if (filteredTransactions.length === 0) {
833
+ list.innerHTML = `
834
+ <div class="empty-state">
835
+ <i class="fas fa-inbox"></i>
836
+ <p>Keine Buchungen vorhanden</p>
837
+ </div>
838
+ `;
839
  return;
840
  }
841
 
842
+ list.innerHTML = filteredTransactions.slice(0, 10).map(t => `
843
+ <div class="transaction-item ${t.type}">
844
  <div class="transaction-info">
845
+ <div>${t.description || t.category}</div>
846
+ <div class="transaction-category">${t.category} • ${formatDate(t.date)}</div>
 
 
 
847
  </div>
848
+ <div class="transaction-amount amount-${t.type}">
849
  ${t.type === 'income' ? '+' : '-'}${formatCurrency(t.amount)}
850
  </div>
851
+ <button class="btn btn-danger" onclick="deleteTransaction(${t.id})" style="padding: 8px 12px;">
852
+ <i class="fas fa-times"></i>
853
  </button>
854
  </div>
855
  `).join('');
856
+ }
857
+
858
+ function deleteTransaction(id) {
859
+ if (confirm('Möchten Sie diese Buchung wirklich löschen?')) {
860
+ transactions = transactions.filter(t => t.id !== id);
861
+ saveTransactions();
862
+ updateUI();
863
+ showToast('Buchung gelöscht', 'success');
864
+ }
865
+ }
866
+
867
+ function filterTransactions(type) {
868
+ currentFilter = type;
869
 
870
+ // Tabs aktualisieren
871
+ document.querySelectorAll('.tab').forEach(tab => {
872
+ tab.classList.remove('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
873
  });
874
+ event.target.classList.add('active');
875
+
876
+ updateTransactionList();
877
+ }
878
 
879
+ function updateCharts() {
880
+ drawCategoryChart();
881
+ drawMonthlyChart();
882
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
883
 
884
+ function drawCategoryChart() {
885
+ const canvas = document.getElementById('categoryChart');
886
+ const ctx = canvas.getContext('2d');
887
+
888
+ // Kategorie-Ausgaben summieren
889
+ const categoryTotals = {};
890
+ transactions.filter(t => t.type === 'expense').forEach(t => {
891
+ categoryTotals[t.category] = (categoryTotals[t.category] || 0) + t.amount;
892
+ });
893
+
894
+ const labels = Object.keys(categoryTotals);
895
+ const data = Object.values(categoryTotals);
896
+
897
+ if (labels.length === 0) {
898
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
899
+ ctx.font = '16px Arial';
900
+ ctx.fillStyle = '#999';
901
+ ctx.textAlign = 'center';
902
+ ctx.fillText('Keine Daten verfügbar', canvas.width / 2, canvas.height / 2);
903
+ return;
904
+ }
905
+
906
+ // Farben generieren
907
+ const colors = labels.map((_, i) => `hsl(${(i * 360) / labels.length}, 70%, 60%)`);
908
+
909
+ // Canvas-Größe setzen
910
+ canvas.width = canvas.offsetWidth;
911
+ canvas.height = 300;
912
+
913
+ // Kuchendiagramm zeichnen
914
+ const centerX = canvas.width / 2;
915
+ const centerY = canvas.height / 2;
916
+ const radius = Math.min(centerX, centerY) - 40;
917
+
918
+ let currentAngle = -Math.PI / 2;
919
+ const total = data.reduce((sum, val) => sum + val, 0);
920
+
921
+ data.forEach((value, i) => {
922
+ const sliceAngle = (value / total) * 2 * Math.PI;
923
+
924
+ // Segment zeichnen
925
+ ctx.beginPath();
926
+ ctx.arc(centerX, centerY, radius, currentAngle, currentAngle + sliceAngle);
927
+ ctx.lineTo(centerX, centerY);
928
+ ctx.fillStyle = colors[i];
929
+ ctx.fill();
930
+
931
+ // Label zeichnen
932
+ const labelAngle = currentAngle + sliceAngle / 2;
933
+ const labelX = centerX + Math.cos(labelAngle) * (radius * 0.7);
934
+ const labelY = centerY + Math.sin(labelAngle) * (radius * 0.7);
935
+
936
+ ctx.fillStyle = 'white';
937
+ ctx.font = 'bold 12px Arial';
938
+ ctx.textAlign = 'center';
939
+ ctx.fillText(`${((value / total) * 100).toFixed(1)}%`, labelX, labelY);
940
+
941
+ currentAngle += sliceAngle;
942
+ });
943
+
944
+ // Legende zeichnen
945
+ let legendY = 20;
946
+ labels.forEach((label, i) => {
947
+ ctx.fillStyle = colors[i];
948
+ ctx.fillRect(10, legendY, 15, 15);
949
+ ctx.fillStyle = '#333';
950
+ ctx.font = '12px Arial';
951
+ ctx.textAlign = 'left';
952
+ ctx.fillText(`${label}: ${formatCurrency(data[i])}`, 30, legendY + 12);
953
+ legendY += 20;
954
  });
 
 
955
  }
956
 
957
+ function drawMonthlyChart() {
958
+ const canvas = document.getElementById('monthlyChart');
959
+ const ctx = canvas.getContext('2d');
960
+
961
+ // Letzte 6 Monate berechnen
962
+ const months = [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
963
  const incomeData = [];
964
  const expenseData = [];
965
 
966
  for (let i = 5; i >= 0; i--) {
967
  const date = new Date();
968
  date.setMonth(date.getMonth() - i);
969
+ const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
970
 
971
+ months.push(date.toLocaleDateString('de-DE', { month: 'short', year: 'numeric' }));
972
+
973
+ const monthIncome = transactions
974
+ .filter(t => t.type === 'income' && t.date.startsWith(monthKey))
975
  .reduce((sum, t) => sum + t.amount, 0);
976
+
977
+ const monthExpense = transactions
978
+ .filter(t => t.type === 'expense' && t.date.startsWith(monthKey))
979
  .reduce((sum, t) => sum + t.amount, 0);
980
 
981
+ incomeData.push(monthIncome);
982
+ expenseData.push(monthExpense);
 
 
983
  }
984
 
985
+ // Canvas-Größe setzen
986
+ canvas.width = canvas.offsetWidth;
987
+ canvas.height = 300;
 
 
 
 
 
 
988
 
989
+ const padding = 40;
990
+ const chartWidth = canvas.width - padding * 2;
991
+ const chartHeight = canvas.height - padding * 2;
992
+ const barWidth = chartWidth / (months.length * 2 + months.length - 1);
993
+ const maxValue = Math.max(...incomeData, ...expenseData, 1);
994
 
995
+ // Achsen zeichnen
996
+ ctx.strokeStyle = '#ddd';
997
+ ctx.lineWidth = 1;
998
+ ctx.beginPath();
999
+ ctx.moveTo(padding, padding);
1000
+ ctx.lineTo(padding, canvas.height - padding);
1001
+ ctx.lineTo(canvas.width - padding, canvas.height - padding);
1002
+ ctx.stroke();
 
 
 
1003
 
1004
+ // Balken zeichnen
1005
+ months.forEach((month, i) => {
1006
+ const x = padding + i * (barWidth * 2 + barWidth);
1007
+
1008
+ // Einnahmen-Balken
1009
+ const incomeHeight = (incomeData[i] / maxValue) * chartHeight;
1010
+ ctx.fillStyle = '#10b981';
1011
+ ctx.fillRect(x, canvas.height - padding - incomeHeight, barWidth, incomeHeight);
1012
 
1013
+ // Ausgaben-Balken
1014
+ const expenseHeight = (expenseData[i] / maxValue) * chartHeight;
1015
+ ctx.fillStyle = '#ef4444';
1016
+ ctx.fillRect(x + barWidth, canvas.height - padding - expenseHeight, barWidth, expenseHeight);
1017
+
1018
+ // Monats-Label
1019
+ ctx.fillStyle = '#666';
1020
+ ctx.font = '11px Arial';
1021
+ ctx.textAlign = 'center';
1022
+ ctx.fillText(month, x + barWidth, canvas.height - padding + 20);
1023
  });
1024
 
1025
+ // Legende
1026
+ ctx.fillStyle = '#10b981';
1027
+ ctx.fillRect(canvas.width - 100, 10, 15, 15);
1028
+ ctx.fillStyle = '#333';
1029
+ ctx.font = '12px Arial';
1030
+ ctx.textAlign = 'left';
1031
+ ctx.fillText('Einnahmen', canvas.width - 80, 22);
 
 
 
1032
 
1033
+ ctx.fillStyle = '#ef4444';
1034
+ ctx.fillRect(canvas.width - 100, 30, 15, 15);
1035
+ ctx.fillStyle = '#333';
1036
+ ctx.fillText('Ausgaben', canvas.width - 80, 42);
 
 
 
 
 
 
 
 
 
 
 
 
1037
  }
1038
 
1039
+ function exportToCSV() {
1040
+ if (transactions.length === 0) {
1041
+ showToast('Keine Daten zum Exportieren', 'error');
1042
+ return;
 
1043
  }
1044
+
1045
+ let csv = 'Typ,Betrag,Kategorie,Datum,Beschreibung\n';
1046
+ transactions.forEach(t => {
1047
+ csv += `${t.type === 'income' ? 'Einnahme' : 'Ausgabe'},${t.amount},${t.category},${t.date},"${t.description || ''}"\n`;
1048
+ });
1049
+
1050
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
1051
+ const link = document.createElement('a');
1052
+ link.href = URL.createObjectURL(blob);
1053
+ link.download = `haushaltsbuch_${new Date().toISOString().split('T')[0]}.csv`;
1054
+ link.click();
1055
+
1056
+ showToast('CSV erfolgreich exportiert', 'success');
1057
  }
1058
 
1059
+ function importFromCSV(event) {
1060
+ const file = event.target.files[0];
1061
+ if (!file) return;
 
 
 
 
 
1062
 
1063
  const reader = new FileReader();
1064
  reader.onload = function(e) {
1065
  try {
1066
  const lines = e.target.result.split('\n');
1067
+ const imported = [];
1068
 
1069
+ for (let i = 1; i < lines.length; i++) {
1070
+ if (lines[i].trim() === '') continue;
1071
 
1072
+ const parts = lines[i].split(',');
1073
+ if (parts.length >= 4) {
1074
  const transaction = {
1075
+ id: Date.now() + i,
1076
+ type: parts[0].trim() === 'Einnahme' ? 'income' : 'expense',
1077
+ amount: parseFloat(parts[1]),
1078
  category: parts[2].trim(),
1079
+ date: parts[3].trim(),
1080
+ description: parts[4] ? parts[4].replace(/"/g, '').trim() : '',
1081
+ timestamp: new Date().toISOString()
1082
  };
1083
+ imported.push(transaction);
 
 
 
1084
  }
1085
+ }
1086
 
1087
+ if (imported.length > 0) {
1088
+ transactions = [...transactions, ...imported];
1089
  saveTransactions();
1090
+ updateUI();
1091
+ showToast(`${imported.length} Buchungen importiert`, 'success');
 
 
 
1092
  } else {
1093
+ showToast('Keine gültigen Daten gefunden', 'error');
1094
+ }
1095
+ } catch (error) {
1096
+ showToast('Fehler beim Importieren der Datei', 'error');
1097
+ }
1098
+ };
1099
+ reader.readAsText(file);
1100
+
1101
+ // File input zurücksetzen
1102
+ event.target.value = '';
1103
+ }
1104
+
1105
+ function clearAllTransactions() {
1106
+ if (confirm('Möchten Sie alle Buchungen wirklich löschen?')) {
1107
+ transactions = [];
1108
+ saveTransactions();
1109
+ updateUI();
1110
+ showToast('Alle Buchungen gelöscht', 'success');
1111
+ }
1112
+ }
1113
+
1114
+ function clearAllData() {
1115
+ if (confirm('Möchten Sie wirklich alle Daten löschen? Diese Aktion kann nicht rückgängig gemacht werden!')) {
1116
+ localStorage.clear();
1117
+ transactions = [];
1118
+ updateUI();
1119
+ showToast('Alle Daten gelöscht', 'success');
1120
+ }
1121
+ }
1122
+
1123
+ function formatCurrency(amount) {
1124
+ return new Intl.NumberFormat('de-DE', {
1125
+ style: 'currency',
1126
+ currency: 'EUR'
1127
+ }).format(amount);
1128
+ }
1129
+
1130
+ function formatDate(dateString) {
1131
+ return new Date(dateString).toLocaleDateString('de-DE', {
1132
+ day: '2-digit',
1133
+ month: '2-digit',
1134
+ year: 'numeric'
1135
+ });
1136
+ }
1137
+
1138
+ function showToast(message, type = 'success') {
1139
+ const toast = document.getElementById('toast');
1140
+ const toastMessage = document.getElementById('toastMessage');
1141
+
1142
+ toast.className = `toast ${type}`;
1143
+ toastMessage.textContent = message;
1144
+ toast.classList.add('show');
1145
+
1146
+ setTimeout(() => {
1147
+ toast.classList.remove('show');
1148
+ }, 300