|
| 1 | +@page "/katalog" |
| 2 | + |
| 3 | +<PageTitle>Klimamaterial – Katalog</PageTitle> |
| 4 | + |
| 5 | +<div class="catalog-page"> |
| 6 | + |
| 7 | + <div class="catalog-header"> |
| 8 | + <div class="catalog-header-left"> |
| 9 | + <h2 class="catalog-title">Vergleich (@ComparedCount)</h2> |
| 10 | + <p class="catalog-subtitle">Böden · <strong>27 Materialien</strong></p> |
| 11 | + </div> |
| 12 | + <div class="catalog-header-right"> |
| 13 | + <button class="btn-filter" @onclick="ToggleFilter"> |
| 14 | + <svg width="15" height="15" viewBox="0 0 15 15" fill="none"> |
| 15 | + <path d="M1 3h13M3 7h9M5 11h5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> |
| 16 | + </svg> |
| 17 | + Filter |
| 18 | + </button> |
| 19 | + <div class="search-wrapper"> |
| 20 | + <svg class="search-icon" width="15" height="15" viewBox="0 0 15 15" fill="none"> |
| 21 | + <circle cx="6.5" cy="6.5" r="5" stroke="#9ca3af" stroke-width="1.4"/> |
| 22 | + <path d="M10.5 10.5 L13.5 13.5" stroke="#9ca3af" stroke-width="1.4" stroke-linecap="round"/> |
| 23 | + </svg> |
| 24 | + <input type="text" |
| 25 | + class="search-input" |
| 26 | + placeholder="Material suchen …" |
| 27 | + @bind="searchTerm" |
| 28 | + @bind:event="oninput" /> |
| 29 | + </div> |
| 30 | + </div> |
| 31 | + </div> |
| 32 | + |
| 33 | + @if (showFilter) |
| 34 | + { |
| 35 | + <div class="filter-panel"> |
| 36 | + <p class="filter-hint">Filter-Optionen (Demo – noch nicht aktiv)</p> |
| 37 | + <div class="filter-tags"> |
| 38 | + <span class="filter-tag active">Alle</span> |
| 39 | + <span class="filter-tag">Unversiegelt</span> |
| 40 | + <span class="filter-tag">Begrünt</span> |
| 41 | + <span class="filter-tag">Teilversiegelt</span> |
| 42 | + <span class="filter-tag">Versiegelt</span> |
| 43 | + </div> |
| 44 | + </div> |
| 45 | + } |
| 46 | + |
| 47 | + <div class="sort-bar"> |
| 48 | + <span class="sort-label">Sortiert nach:</span> |
| 49 | + <span class="sort-value">Übereinstimmung mit Gewichtung</span> |
| 50 | + <svg width="12" height="12" viewBox="0 0 12 12" fill="none" class="sort-icon"> |
| 51 | + <path d="M2 4l4 4 4-4" stroke="#6b7280" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> |
| 52 | + </svg> |
| 53 | + </div> |
| 54 | + |
| 55 | + <div class="table-wrapper"> |
| 56 | + <table class="material-table"> |
| 57 | + <thead> |
| 58 | + <tr> |
| 59 | + <th class="col-material">Material</th> |
| 60 | + <th class="col-rating"> |
| 61 | + Kühlwirkung |
| 62 | + <small class="col-hint">1 = keine · 6 = stark</small> |
| 63 | + </th> |
| 64 | + <th class="col-rating"> |
| 65 | + Albedo |
| 66 | + <small class="col-hint">1 = dunkel · 6 = hell</small> |
| 67 | + </th> |
| 68 | + <th class="col-rating"> |
| 69 | + THG-Emission |
| 70 | + <small class="col-hint">1 = hoch · 6 = gering</small> |
| 71 | + </th> |
| 72 | + <th class="col-rating"> |
| 73 | + Versickerung |
| 74 | + <small class="col-hint">1 = keine · 6 = hoch</small> |
| 75 | + </th> |
| 76 | + <th class="col-rating"> |
| 77 | + Lebensdauer |
| 78 | + <small class="col-hint">1 = kurz · 6 = lang</small> |
| 79 | + </th> |
| 80 | + <th class="col-match">Übereinstimmung</th> |
| 81 | + <th class="col-action">Aktion</th> |
| 82 | + </tr> |
| 83 | + </thead> |
| 84 | + <tbody> |
| 85 | + @foreach (var m in FilteredMaterials) |
| 86 | + { |
| 87 | + <tr class="@(m.IsCompared ? "row-compared" : "")"> |
| 88 | + <td class="cell-material"> |
| 89 | + <span class="material-name">@m.Name</span> |
| 90 | + <span class="material-cat">@m.Kategorie</span> |
| 91 | + </td> |
| 92 | + <td class="cell-rating">@((MarkupString)RatingDots(m.Kuehlwirkung))</td> |
| 93 | + <td class="cell-rating">@((MarkupString)RatingDots(m.Albedo))</td> |
| 94 | + <td class="cell-rating">@((MarkupString)RatingDots(m.ThgEmission))</td> |
| 95 | + <td class="cell-rating">@((MarkupString)RatingDots(m.Versickerung))</td> |
| 96 | + <td class="cell-rating">@((MarkupString)RatingDots(m.Lebensdauer))</td> |
| 97 | + <td class="cell-match"> |
| 98 | + <div class="match-score-row"> |
| 99 | + <span class="match-points">@m.UebereinstimmungPunkte</span> |
| 100 | + <span class="match-max">/ @m.UebereinstimmungMax</span> |
| 101 | + </div> |
| 102 | + <div class="match-bar-track"> |
| 103 | + <div class="match-bar-fill" |
| 104 | + style="width: @(m.UebereinstimmungPunkte * 100 / m.UebereinstimmungMax)%"> |
| 105 | + </div> |
| 106 | + </div> |
| 107 | + </td> |
| 108 | + <td class="cell-action"> |
| 109 | + <button class="compare-btn @(m.IsCompared ? "compared" : "")" |
| 110 | + @onclick="() => ToggleCompare(m)"> |
| 111 | + @(m.IsCompared ? "✓ Im Vergleich" : "+ Vergleichen") |
| 112 | + </button> |
| 113 | + </td> |
| 114 | + </tr> |
| 115 | + } |
| 116 | + @if (!FilteredMaterials.Any()) |
| 117 | + { |
| 118 | + <tr> |
| 119 | + <td colspan="8" class="no-results"> |
| 120 | + Keine Materialien gefunden für „@searchTerm" |
| 121 | + </td> |
| 122 | + </tr> |
| 123 | + } |
| 124 | + </tbody> |
| 125 | + </table> |
| 126 | + </div> |
| 127 | + |
| 128 | + <div class="pagination"> |
| 129 | + <button class="page-btn active">1</button> |
| 130 | + <button class="page-btn">2</button> |
| 131 | + <button class="page-btn">3</button> |
| 132 | + <span class="page-ellipsis">…</span> |
| 133 | + </div> |
| 134 | + |
| 135 | +</div> |
| 136 | + |
| 137 | +@code { |
| 138 | + private string searchTerm = ""; |
| 139 | + private bool showFilter = false; |
| 140 | + |
| 141 | + private int ComparedCount => Materials.Count(m => m.IsCompared); |
| 142 | + |
| 143 | + private IEnumerable<MaterialItem> FilteredMaterials => |
| 144 | + string.IsNullOrWhiteSpace(searchTerm) |
| 145 | + ? Materials |
| 146 | + : Materials.Where(m => |
| 147 | + m.Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase) || |
| 148 | + m.Kategorie.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)); |
| 149 | + |
| 150 | + private void ToggleCompare(MaterialItem m) => m.IsCompared = !m.IsCompared; |
| 151 | + private void ToggleFilter() => showFilter = !showFilter; |
| 152 | + |
| 153 | + private static string RatingDots(int value) |
| 154 | + { |
| 155 | + var sb = new System.Text.StringBuilder(); |
| 156 | + sb.Append("<div class=\"rating-dots\">"); |
| 157 | + for (int i = 1; i <= 6; i++) |
| 158 | + { |
| 159 | + sb.Append(i <= value |
| 160 | + ? "<span class=\"dot dot-on\"></span>" |
| 161 | + : "<span class=\"dot dot-off\"></span>"); |
| 162 | + } |
| 163 | + sb.Append("</div>"); |
| 164 | + return sb.ToString(); |
| 165 | + } |
| 166 | + |
| 167 | + private readonly List<MaterialItem> Materials = new() |
| 168 | + { |
| 169 | + new() { Id = 1, Name = "Kiesbelag", Kategorie = "Boden · unversiegelt", Kuehlwirkung = 5, Albedo = 3, ThgEmission = 6, Versickerung = 6, Lebensdauer = 3, UebereinstimmungPunkte = 44, UebereinstimmungMax = 54 }, |
| 170 | + new() { Id = 2, Name = "Rasen", Kategorie = "Boden · begrünt", Kuehlwirkung = 6, Albedo = 3, ThgEmission = 6, Versickerung = 3, Lebensdauer = 2, UebereinstimmungPunkte = 42, UebereinstimmungMax = 54 }, |
| 171 | + new() { Id = 3, Name = "Rasengittersteinpflästerung", Kategorie = "Boden · teilversiegelt", Kuehlwirkung = 4, Albedo = 3, ThgEmission = 3, Versickerung = 5, Lebensdauer = 5, UebereinstimmungPunkte = 38, UebereinstimmungMax = 54 }, |
| 172 | + new() { Id = 4, Name = "Steinplattenpflästerung", Kategorie = "Boden · versiegelt", Kuehlwirkung = 2, Albedo = 2, ThgEmission = 2, Versickerung = 1, Lebensdauer = 6, UebereinstimmungPunkte = 32, UebereinstimmungMax = 54 }, |
| 173 | + new() { Id = 5, Name = "Asphalt dunkel", Kategorie = "Boden · versiegelt", Kuehlwirkung = 1, Albedo = 1, ThgEmission = 2, Versickerung = 1, Lebensdauer = 5, UebereinstimmungPunkte = 22, UebereinstimmungMax = 54 }, |
| 174 | + new() { Id = 6, Name = "Betonbelag", Kategorie = "Boden · versiegelt", Kuehlwirkung = 2, Albedo = 3, ThgEmission = 2, Versickerung = 1, Lebensdauer = 6, UebereinstimmungPunkte = 28, UebereinstimmungMax = 54 }, |
| 175 | + }; |
| 176 | + |
| 177 | + private record MaterialItem |
| 178 | + { |
| 179 | + public int Id { get; init; } |
| 180 | + public string Name { get; init; } = ""; |
| 181 | + public string Kategorie { get; init; } = ""; |
| 182 | + public int Kuehlwirkung { get; init; } |
| 183 | + public int Albedo { get; init; } |
| 184 | + public int ThgEmission { get; init; } |
| 185 | + public int Versickerung { get; init; } |
| 186 | + public int Lebensdauer { get; init; } |
| 187 | + public int UebereinstimmungPunkte { get; init; } |
| 188 | + public int UebereinstimmungMax { get; init; } |
| 189 | + public bool IsCompared { get; set; } |
| 190 | + } |
| 191 | +} |
0 commit comments