Files
miniplan/public/src/components/ListView.vue
T

374 lines
9.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import {ref, watch} from "vue"
import type {Gottesdienst, Mark, SimplifiedMinistrant} from "@/models/models"
import GottesdienstCard from "@/components/GottesdienstCard.vue"
const STORAGE_KEY = "miniplan_list_tutorial_seen"
const props = defineProps<{
gottesdienste: Gottesdienst[]
ministranten: SimplifiedMinistrant[]
marks: Mark[]
editable: string[]
currentUsername: string
admin: boolean
}>()
const emit = defineEmits<{
toggleMark: [gid: number, mid: number]
addGottesdienst: []
editGottesdienst: [id: number]
addMinistrant: []
editMinistrant: [id: number]
importZelebrationsplan: []
}>()
const activeTab = ref<"gottesdienste" | "ministranten">("gottesdienste")
const showTutorial = ref(!localStorage.getItem(STORAGE_KEY))
function dismissTutorial() {
showTutorial.value = false
localStorage.setItem(STORAGE_KEY, "true")
}
function countXMarks(miniId: number): number {
return props.marks.filter(m => m.mid === miniId && m.value === 1).length
}
</script>
<template>
<div class="list-view">
<div class="tab-bar" v-if="admin">
<button
class="tab"
:class="{ active: activeTab === 'gottesdienste' }"
@click="activeTab = 'gottesdienste'"
><i class="icon">list</i> Gottesdienste</button>
<button
class="tab"
:class="{ active: activeTab === 'ministranten' }"
@click="activeTab = 'ministranten'"
><i class="icon">groups</i> Ministranten</button>
</div>
<template v-if="activeTab === 'gottesdienste'">
<GottesdienstCard
v-for="godi in gottesdienste"
:key="godi.id"
:gottesdienst="godi"
:ministranten="ministranten"
:marks="marks"
:editable="editable"
:current-username="currentUsername"
@toggle-mark="(gid, mid) => emit('toggleMark', gid, mid)"
@edit-gottesdienst="(id) => emit('editGottesdienst', id)"
/>
<button v-if="admin" class="add-godi" @click="emit('addGottesdienst')">
<i class="icon">add</i> Gottesdienst
</button>
<button v-if="admin" class="import-btn" @click="emit('importZelebrationsplan')">
<i class="icon">upload</i> Zelebrationsplan importieren
</button>
</template>
<template v-if="admin && activeTab === 'ministranten'">
<div class="mini-list">
<div v-for="mini in ministranten" :key="mini.id" class="mini-row">
<span class="mini-name">{{ mini.firstname }} {{ mini.lastname }}</span>
<span class="mini-username">@{{ mini.username }}</span>
<span class="x-count">{{ countXMarks(mini.id) }}</span>
<button v-if="admin" class="edit-btn" @click="emit('editMinistrant', mini.id)">
<i class="icon">edit</i>
</button>
</div>
<div v-if="ministranten.length === 0" class="empty-state">
Keine Ministranten
</div>
<button v-if="admin" class="add-mini" @click="emit('addMinistrant')">
<i class="icon">add</i> Ministrant
</button>
</div>
</template>
<div class="tutorial-overlay" v-if="showTutorial" @click.self="dismissTutorial">
<div class="tutorial-card">
<h3><i class="icon">info</i> Listenansicht</h3>
<ul>
<li><strong>Gottesdienste</strong> sind als Karten dargestellt jede Karte zeigt Datum, Uhrzeit und Rückmeldungen.</li>
<li>Klicke auf dein <strong>Kästchen</strong> in der Karte, um deine Teilnahme anzugeben: <span class="chip cross">✓</span> dabei, <span class="chip minus">✗</span> nicht dabei.</li>
<li>Die <strong>Rückmeldungen</strong> der anderen siehst du aufgeklappt unter „X Rückmeldungen".</li>
<li v-if="admin">Als Admin siehst du zusätzlich den <strong>Ministranten-Tab</strong> und kannst Markierungen direkt anklicken.</li>
</ul>
<button @click="dismissTutorial">Verstanden!</button>
</div>
</div>
</div>
</template>
<style scoped lang="less">
.list-view {
padding: 12px;
max-width: 800px;
margin: 0 auto;
@media(min-width: 800px) {
}
}
.tab-bar {
display: flex;
gap: 4px;
margin-bottom: 12px;
background: #f0f0f0;
border-radius: 8px;
padding: 3px;
.tab {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 8px;
font-size: 14px;
font-weight: 600;
border: none;
border-radius: 6px;
background: transparent;
color: #777;
cursor: pointer;
transition: 100ms;
i {
font-size: 20px;
}
&.active {
background: #ffffff;
color: #222;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
&:not(.active):hover {
color: #555;
}
}
}
.mini-list {
background: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
.mini-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.mini-name {
font-size: 15px;
font-weight: 500;
}
.mini-username {
font-size: 13px;
color: #999;
flex: 1;
}
.x-count {
font-size: 13px;
font-weight: 700;
color: #045b04;
background: #d1fcd1;
padding: 1px 8px;
border-radius: 10px;
margin: 0 8px;
}
}
.mini-row {
.edit-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0;
margin: 0;
border: none;
border-radius: 4px;
background: transparent;
color: #aaa;
cursor: pointer;
transition: 100ms;
flex-shrink: 0;
i {
font-size: 18px;
margin: 0;
}
&:hover {
background: #f0f0f0;
color: #555;
}
}
}
.empty-state {
padding: 24px;
text-align: center;
color: #999;
font-size: 14px;
}
.add-mini {
display: flex;
width: 100%;
padding: 10px;
font-size: 14px;
justify-content: center;
background: #fafafa;
border: none;
border-top: 1px solid #f0f0f0;
border-radius: 0;
cursor: pointer;
color: #777;
transition: 100ms;
margin: 0;
i {
font-size: 20px;
margin-right: 6px;
}
&:hover {
background: #f0f0f0;
color: #444;
}
}
}
.add-godi {
display: flex;
width: 100%;
margin: 16px 0;
padding: 12px;
font-size: 15px;
justify-content: center;
background: #f5f5f5;
border: 1px dashed #c4c4c4;
border-radius: 8px;
cursor: pointer;
color: #555;
transition: 100ms;
i {
font-size: 22px;
margin-right: 6px;
}
&:hover {
background: #eaeaea;
border-color: #999;
color: #333;
}
}
.tutorial-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.35);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 16px;
.tutorial-card {
background: #fff;
border-radius: 12px;
padding: 28px 32px;
max-width: 440px;
width: 100%;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
h3 {
display: flex;
align-items: center;
gap: 8px;
margin: 0 0 16px;
font-size: 20px;
i {
font-size: 26px;
color: #555;
}
}
ul {
margin: 0 0 20px;
padding: 0 0 0 18px;
line-height: 1.7;
font-size: 15px;
color: #333;
li {
margin-bottom: 6px;
}
}
.chip {
display: inline-block;
padding: 0 6px;
border-radius: 4px;
font-size: 13px;
font-weight: 700;
&.cross { background: #d1fcd1; color: #045b04; }
&.minus { background: #fdd5d5; color: #690b0b; }
}
button {
display: flex;
margin-left: auto;
}
}
}
.import-btn {
display: flex;
width: 100%;
margin: 16px 0;
padding: 12px;
font-size: 15px;
justify-content: center;
background: #f5f5f5;
border: 1px dashed #c4c4c4;
border-radius: 8px;
cursor: pointer;
color: #555;
transition: 100ms;
i {
font-size: 22px;
margin-right: 6px;
}
&:hover {
background: #eaeaea;
border-color: #999;
color: #333;
}
}
</style>