From d8ca8d743c1a1f014cc466e8f43f053f1a020b93 Mon Sep 17 00:00:00 2001 From: Jonas Gerg Date: Tue, 14 Jul 2026 21:22:58 +0200 Subject: [PATCH 01/26] Make birthday nullable in ministrant model, DAO, and DB schema --- .../src/main/kotlin/de/walamana/models/Ministranten.kt | 10 +++++----- .../src/main/kotlin/de/walamana/plugins/Databases.kt | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/private/minis-backend/src/main/kotlin/de/walamana/models/Ministranten.kt b/private/minis-backend/src/main/kotlin/de/walamana/models/Ministranten.kt index 25cbec5..97b06a6 100644 --- a/private/minis-backend/src/main/kotlin/de/walamana/models/Ministranten.kt +++ b/private/minis-backend/src/main/kotlin/de/walamana/models/Ministranten.kt @@ -20,7 +20,7 @@ data class Ministrant( val passwordHash: String? = "", val firstname: String, val lastname: String, - val birthday: DateAsLong, + val birthday: DateAsLong? = null, val privileges: CommaSeperatedStringList ) @@ -38,7 +38,7 @@ object Ministranten : Table() { val passwordHash = varchar("passwordHash", 1024) val firstname = varchar("firstname", 128) val lastname = varchar("lastname", 128) - val birthday = long("birthday") + val birthday = long("birthday").nullable() val privileges = varchar("privileges", 1024) override val primaryKey = PrimaryKey(id) @@ -51,7 +51,7 @@ object MinistrantenDao { if (showPasswordHash) row[Ministranten.passwordHash] else "", row[Ministranten.firstname], row[Ministranten.lastname], - Date(row[Ministranten.birthday]), + row[Ministranten.birthday]?.let(::Date), row[Ministranten.privileges].split(",") ) @@ -96,7 +96,7 @@ object MinistrantenDao { passwordHash: String, firstname: String, lastname: String, - birthday: Date, + birthday: Date?, privileges: List ) = dbQuery { val statement = Ministranten.insert { @@ -104,7 +104,7 @@ object MinistrantenDao { it[Ministranten.passwordHash] = "" it[Ministranten.firstname] = firstname it[Ministranten.lastname] = lastname - it[Ministranten.birthday] = birthday.time + it[Ministranten.birthday] = birthday?.time it[Ministranten.privileges] = privileges.joinToString(",") } Security.setPassword(username, passwordHash) diff --git a/private/minis-backend/src/main/kotlin/de/walamana/plugins/Databases.kt b/private/minis-backend/src/main/kotlin/de/walamana/plugins/Databases.kt index 65fb568..a0a4d2e 100644 --- a/private/minis-backend/src/main/kotlin/de/walamana/plugins/Databases.kt +++ b/private/minis-backend/src/main/kotlin/de/walamana/plugins/Databases.kt @@ -29,6 +29,7 @@ fun Application.configureDatabases() { SchemaUtils.create(GottesdienstGroups) SchemaUtils.create(Gottesdienste) SchemaUtils.create(Marks) + exec("ALTER TABLE MINISTRANTEN ALTER COLUMN birthday BIGINT NULL;") } } -- 2.54.0 From ad29d0682ef04b1ca45c424404e6f6237e8f3b05 Mon Sep 17 00:00:00 2001 From: Jonas Gerg Date: Tue, 14 Jul 2026 21:51:55 +0200 Subject: [PATCH 02/26] feat: add list view with GottesdienstCard components - New GottesdienstCard component shows each service with its info - Shows the currently logged-in user's mark as a toggle checkbox - Lists ministrants who have responded with their mark status - Admin view hides the checkbox, shows responses only - New ListView wrapper renders all cards - PlanView gets a Tabelle/Liste toggle between table and card views --- public/src/components/GottesdienstCard.vue | 345 +++++++++++++++++++++ public/src/components/ListView.vue | 37 +++ public/src/views/PlanView.vue | 111 +++++-- 3 files changed, 470 insertions(+), 23 deletions(-) create mode 100644 public/src/components/GottesdienstCard.vue create mode 100644 public/src/components/ListView.vue diff --git a/public/src/components/GottesdienstCard.vue b/public/src/components/GottesdienstCard.vue new file mode 100644 index 0000000..f57e05b --- /dev/null +++ b/public/src/components/GottesdienstCard.vue @@ -0,0 +1,345 @@ + + + + + diff --git a/public/src/components/ListView.vue b/public/src/components/ListView.vue new file mode 100644 index 0000000..ff9f6d2 --- /dev/null +++ b/public/src/components/ListView.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/public/src/views/PlanView.vue b/public/src/views/PlanView.vue index deb49d0..d86497f 100644 --- a/public/src/views/PlanView.vue +++ b/public/src/views/PlanView.vue @@ -5,6 +5,7 @@ import TablePlan from "@/components/TablePlan.vue"; import {API, UnauthorizedError} from "@/services/api"; import type {Gottesdienst, GottesdienstGroup, Mark, SimplifiedMinistrant} from "@/models/models"; import MobilePlan from "@/components/MobilePlan.vue"; +import ListView from "@/components/ListView.vue"; import PlanActionBar from "@/components/PlanActionBar.vue"; import {Auth} from "@/services/auth"; import GroupView from "@/components/GroupView.vue"; @@ -38,6 +39,8 @@ const plan = reactive<{ groups: [] }) const mobile = ref(window.innerWidth <= MAX_WIDTH_MOBILE) +const viewMode = ref<"table" | "list">("table") +const currentUsername = ref("") const editedMarks = reactive([]); const editPlanAdmin = ref(false) const planId = ref(parseInt(route.params.id as string)) @@ -79,6 +82,7 @@ async function loadPlan() { Auth.loggedInSubject.subscribe(async (loggedIn) => { console.log("logged in " + loggedIn) if (loggedIn) { + currentUsername.value = Auth.getUser() plan.editable = Auth.getPrivileges() if (Auth.getUser() == "admin") { editPlanAdmin.value = true @@ -86,6 +90,7 @@ Auth.loggedInSubject.subscribe(async (loggedIn) => { console.log("Plan", plan.ministranten) } } else { + currentUsername.value = "" editPlanAdmin.value = false plan.editable = [] } @@ -312,40 +317,70 @@ async function importZelebrationsplan() { @@ -34,4 +39,30 @@ const emit = defineEmits<{ .list-view { padding: 12px; } + +.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; + } +} diff --git a/public/src/views/PlanView.vue b/public/src/views/PlanView.vue index d966e8e..5f29602 100644 --- a/public/src/views/PlanView.vue +++ b/public/src/views/PlanView.vue @@ -342,7 +342,9 @@ async function importZelebrationsplan() { :marks="getMarks()" :editable="plan.editable" :current-username="currentUsername" + :admin="editPlanAdmin" @toggle-mark="toggleMark" + @add-gottesdienst="createGottesdienst()" class="plan list" v-if="viewMode === 'list'"> -- 2.54.0 From 9e70dea85aad9feb29f570cd7d8885c6c5706f62 Mon Sep 17 00:00:00 2001 From: Jonas Gerg Date: Tue, 14 Jul 2026 21:59:59 +0200 Subject: [PATCH 06/26] feat: add tabs to ListView for Gottesdienste and Ministranten - Tab bar with two tabs at top of list view - Gottesdienste tab shows the existing card list - Ministranten tab shows a simple list of all ministrants with name and username --- public/src/components/ListView.vue | 132 ++++++++++++++++++++++++++--- 1 file changed, 119 insertions(+), 13 deletions(-) diff --git a/public/src/components/ListView.vue b/public/src/components/ListView.vue index 7f67b11..cd8351e 100644 --- a/public/src/components/ListView.vue +++ b/public/src/components/ListView.vue @@ -1,4 +1,5 @@ @@ -40,6 +70,82 @@ const emit = defineEmits<{ padding: 12px; } +.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; + } + } + + .empty-state { + padding: 24px; + text-align: center; + color: #999; + font-size: 14px; + } +} + .add-godi { display: flex; width: 100%; -- 2.54.0 From 39929361a0bf3aff32842a3e94ac0de0da4e63d5 Mon Sep 17 00:00:00 2001 From: Jonas Gerg Date: Tue, 14 Jul 2026 22:01:15 +0200 Subject: [PATCH 07/26] feat: add add/edit ministrant actions in ListView - Each ministrant row shows an edit button for admin - Bottom of ministrant list has an add button - Events wired to PlanView's createMinistrant dialog --- public/src/components/ListView.vue | 63 ++++++++++++++++++++++++++++++ public/src/views/PlanView.vue | 2 + 2 files changed, 65 insertions(+) diff --git a/public/src/components/ListView.vue b/public/src/components/ListView.vue index cd8351e..53702bd 100644 --- a/public/src/components/ListView.vue +++ b/public/src/components/ListView.vue @@ -15,6 +15,8 @@ defineProps<{ const emit = defineEmits<{ toggleMark: [gid: number, mid: number] addGottesdienst: [] + addMinistrant: [] + editMinistrant: [id: number] }>() const activeTab = ref<"gottesdienste" | "ministranten">("gottesdienste") @@ -56,10 +58,16 @@ const activeTab = ref<"gottesdienste" | "ministranten">("gottesdienste")
{{ mini.firstname }} {{ mini.lastname }} @{{ mini.username }} +
Keine Ministranten
+ @@ -138,12 +146,67 @@ const activeTab = ref<"gottesdienste" | "ministranten">("gottesdienste") } } + .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 { diff --git a/public/src/views/PlanView.vue b/public/src/views/PlanView.vue index 5f29602..9ea55fa 100644 --- a/public/src/views/PlanView.vue +++ b/public/src/views/PlanView.vue @@ -345,6 +345,8 @@ async function importZelebrationsplan() { :admin="editPlanAdmin" @toggle-mark="toggleMark" @add-gottesdienst="createGottesdienst()" + @add-ministrant="createMinistrant()" + @edit-ministrant="(id) => createMinistrant(id)" class="plan list" v-if="viewMode === 'list'"> -- 2.54.0 From 7e5fa85a0ae2e55fb3f1c2f69f477d805bf11be2 Mon Sep 17 00:00:00 2001 From: Jonas Gerg Date: Tue, 14 Jul 2026 22:05:00 +0200 Subject: [PATCH 08/26] feat: ministrant list tab with add/edit, action bar at top --- docker-compose.yml | 2 ++ private/minis-backend/.env | 2 ++ private/minis-backend/application_base.conf | 6 +++--- public/src/components/PlanActionBar.vue | 6 ++++++ public/src/services/api.ts | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index eb5128f..c29f97a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,6 +14,8 @@ services: - POSTGRES_PASSWORD=minis - POSTGRES_USER=minis - POSTGRES_DB=minis + ports: + - 5432:5432 networks: - backend volumes: diff --git a/private/minis-backend/.env b/private/minis-backend/.env index 0805f7f..7a59ace 100644 --- a/private/minis-backend/.env +++ b/private/minis-backend/.env @@ -11,3 +11,5 @@ DATABASE_PASSWORD=abc ADMIN_PASSWORD=123 FRONTEND_PATH=./public APPLICATION_NAME=Miniplan\ Hl.\ Familie +PORT=8080 + diff --git a/private/minis-backend/application_base.conf b/private/minis-backend/application_base.conf index 86b86ba..62f770f 100644 --- a/private/minis-backend/application_base.conf +++ b/private/minis-backend/application_base.conf @@ -1,7 +1,7 @@ ktor { development = true deployment { - port = "${PORT}" + port = ${PORT} } application { modules = [ de.walamana.ApplicationKt.module ] @@ -9,7 +9,7 @@ ktor { } jwt { - secret = "${SECRET}" + secret = ${SECRET} issuer = "http://0.0.0.0:8080/" audience = "http://0.0.0.0:8080/" realm = "mini-data" @@ -21,5 +21,5 @@ database { } admin { - password = "${ADMIN}" + password = ${ADMIN} } diff --git a/public/src/components/PlanActionBar.vue b/public/src/components/PlanActionBar.vue index a23b438..cc302c1 100644 --- a/public/src/components/PlanActionBar.vue +++ b/public/src/components/PlanActionBar.vue @@ -36,6 +36,12 @@ const props = defineProps<{ transition: 200ms translate; translate: 220px; transition-delay: 100ms; + display: flex; + gap: 6px; + flex-wrap: wrap; + button { + margin: 0; + } }; &.save .other-action { diff --git a/public/src/services/api.ts b/public/src/services/api.ts index 8bc58b7..5c30ba4 100644 --- a/public/src/services/api.ts +++ b/public/src/services/api.ts @@ -3,7 +3,7 @@ import {Auth} from "@/services/auth"; const API_ENDPOINT = import.meta.env.MODE == "development" - ? "http://0.0.0.0:8080/api" + ? "http://localhost:8080/api" : window.location.origin + "/api" -- 2.54.0 From edaa5fe94fce8f3ac798b5f0ee305860833bbe69 Mon Sep 17 00:00:00 2001 From: Jonas Gerg Date: Tue, 14 Jul 2026 22:05:52 +0200 Subject: [PATCH 09/26] feat: clickable marks for admin, mark counts, collapsible card list - Admin can click a ministrant's mark badge to cycle through states - Each ministrant shows total marks count across all gottesdienste - Ministrant list in cards starts collapsed showing only count, expands on click --- public/src/components/GottesdienstCard.vue | 88 ++++++++++++++++++---- 1 file changed, 73 insertions(+), 15 deletions(-) diff --git a/public/src/components/GottesdienstCard.vue b/public/src/components/GottesdienstCard.vue index f57e05b..de64e19 100644 --- a/public/src/components/GottesdienstCard.vue +++ b/public/src/components/GottesdienstCard.vue @@ -1,6 +1,6 @@