diff --git a/app/src/lib/icons.ts b/app/src/lib/icons.ts index 3d8cb16..bf74341 100644 --- a/app/src/lib/icons.ts +++ b/app/src/lib/icons.ts @@ -4,6 +4,7 @@ import calendar from './icons/calendar.svg?raw'; import currencyEur from './icons/currency-eur.svg?raw'; import envelope from './icons/envelope.svg?raw'; import gear from './icons/gear.svg?raw'; +import images from './icons/images.svg?raw'; export const icons = { house, @@ -12,6 +13,7 @@ export const icons = { 'currency-eur': currencyEur, envelope, gear, + images, } as const; export type IconName = keyof typeof icons; diff --git a/app/src/lib/icons/images.svg b/app/src/lib/icons/images.svg new file mode 100644 index 0000000..48ffc9a --- /dev/null +++ b/app/src/lib/icons/images.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/lib/types.ts b/app/src/lib/types.ts index bcc34d3..067d0e6 100644 --- a/app/src/lib/types.ts +++ b/app/src/lib/types.ts @@ -43,6 +43,24 @@ export interface Mitglied { } export type Rolle = 'admin' | 'trainer'; + +export interface Neuigkeit { + id: string; + verein_id: string; + autor_id: string; + text?: string; + medien: string[]; + gruppe_ids: string[]; + termin_id?: string; + created: string; + expand?: { autor_id?: { id: string; name: string } }; +} + +export interface Reaktion { + id: string; + beitrag_id: string; + user_id: string; +} export type Verfuegbarkeit = 'offen' | 'bestaetigt' | 'abgesagt' | 'vertretung_gesucht'; export interface Gruppe { diff --git a/app/src/routes/(app)/+layout.svelte b/app/src/routes/(app)/+layout.svelte index 25ea770..fcd5e9c 100644 --- a/app/src/routes/(app)/+layout.svelte +++ b/app/src/routes/(app)/+layout.svelte @@ -70,6 +70,7 @@ const isAdmin = () => !pb.authStore.record?.rolle || pb.authStore.record?.rolle === 'admin'; const allNavItems: { href: string; label: string; icon: IconName; adminOnly?: boolean }[] = [ + { href: '/neuigkeiten', label: 'Neuigkeiten', icon: 'images' }, { href: '/mitglieder', label: 'Mitglieder', icon: 'users' }, { href: '/termine', label: 'Termine', icon: 'calendar' }, { href: '/beitraege', label: 'Beiträge', icon: 'currency-eur', adminOnly: true }, diff --git a/app/src/routes/(app)/neuigkeiten/+page.svelte b/app/src/routes/(app)/neuigkeiten/+page.svelte new file mode 100644 index 0000000..4591d55 --- /dev/null +++ b/app/src/routes/(app)/neuigkeiten/+page.svelte @@ -0,0 +1,479 @@ + + +Neuigkeiten — vereins.haus + +
+

Neuigkeiten

+ {#if canPost()} + + {/if} +
+ +{#if loading} +

Laden…

+{:else if neuigkeiten.length === 0} +

Noch keine Beiträge – schreib den ersten!

+{:else} +
+ {#each neuigkeiten as n (n.id)} +
+
+
{autorName(n)[0]?.toUpperCase()}
+
+ {autorName(n)} + {zeitAgo(n.created)} +
+ {#if n.autor_id === userId()} + + {/if} +
+ + {#if n.termin_id} +
📅 {terminName(n.termin_id)}
+ {/if} + + {#if n.text} +

{n.text}

+ {/if} + + {#if n.medien?.length > 0} +
+ {#each n.medien as datei (datei)} + {#if isVideo(datei)} + + {:else} + + {/if} + {/each} +
+ {/if} + +
+ + {#if n.gruppe_ids?.length > 0} + + {n.gruppe_ids.length === 1 + ? (gruppen.find(g => g.id === n.gruppe_ids[0])?.name ?? '') + : `${n.gruppe_ids.length} Gruppen`} + + {/if} +
+
+ {/each} +
+{/if} + + +{#if showForm} + +{/if} + + +{#if lightboxUrl} + +{/if} + + diff --git a/pocketbase/pb_migrations/1779230900_neuigkeiten.js b/pocketbase/pb_migrations/1779230900_neuigkeiten.js new file mode 100644 index 0000000..752dea6 --- /dev/null +++ b/pocketbase/pb_migrations/1779230900_neuigkeiten.js @@ -0,0 +1,98 @@ +/// +migrate((app) => { + + // neuigkeiten – Vereins-Feed mit Medien + { + const c = new Collection({ + "createRule": "@request.auth.verein_id = verein_id", + "deleteRule": "@request.auth.verein_id = verein_id && autor_id = @request.auth.id", + "listRule": "@request.auth.verein_id = verein_id", + "viewRule": "@request.auth.verein_id = verein_id", + "updateRule": "@request.auth.verein_id = verein_id && autor_id = @request.auth.id", + "fields": [ + { + "autogeneratePattern": "[a-z0-9]{15}", "id": "text3208210256", + "max": 15, "min": 15, "name": "id", "pattern": "^[a-z0-9]+$", + "primaryKey": true, "required": true, "system": true, "type": "text", + "help": "", "hidden": false, "presentable": false + }, + { + "type": "relation", "id": "relation2001000200", "name": "verein_id", + "help": "", "hidden": false, "presentable": false, "required": true, "system": false, + "cascadeDelete": true, "collectionId": "pbc_3589557411", "maxSelect": 1, "minSelect": 0 + }, + { + "type": "relation", "id": "relation2001000201", "name": "autor_id", + "help": "", "hidden": false, "presentable": false, "required": true, "system": false, + "cascadeDelete": false, "collectionId": "_pb_users_auth_", "maxSelect": 1, "minSelect": 0 + }, + { + "type": "text", "id": "text2001000202", "name": "text", + "help": "", "hidden": false, "presentable": false, "required": false, "system": false, + "autogeneratePattern": "", "min": 0, "max": 0, "pattern": "" + }, + { + "type": "file", "id": "file2001000203", "name": "medien", + "help": "", "hidden": false, "presentable": false, "required": false, "system": false, + "maxSelect": 10, "maxSize": 15728640, + "mimeTypes": ["image/jpeg","image/png","image/gif","image/webp","video/mp4","video/quicktime"] + }, + { + "type": "relation", "id": "relation2001000204", "name": "gruppe_ids", + "help": "", "hidden": false, "presentable": false, "required": false, "system": false, + "cascadeDelete": false, "collectionId": "pbc_3099069179", "maxSelect": 99, "minSelect": 0 + }, + { + "type": "relation", "id": "relation2001000205", "name": "termin_id", + "help": "", "hidden": false, "presentable": false, "required": false, "system": false, + "cascadeDelete": false, "collectionId": "pbc_2279568741", "maxSelect": 1, "minSelect": 0 + } + ], + "id": "pbc_neuigkeiten", + "indexes": [], + "name": "neuigkeiten", + "system": false, + "type": "base" + }) + app.save(c) + } + + // reaktionen – 👍 pro User pro Beitrag + { + const c = new Collection({ + "createRule": "@request.auth.id != ''", + "deleteRule": "@request.auth.id = user_id", + "listRule": "@request.auth.verein_id = beitrag_id.verein_id", + "viewRule": "@request.auth.verein_id = beitrag_id.verein_id", + "updateRule": null, + "fields": [ + { + "autogeneratePattern": "[a-z0-9]{15}", "id": "text3208210256", + "max": 15, "min": 15, "name": "id", "pattern": "^[a-z0-9]+$", + "primaryKey": true, "required": true, "system": true, "type": "text", + "help": "", "hidden": false, "presentable": false + }, + { + "type": "relation", "id": "relation2001000210", "name": "beitrag_id", + "help": "", "hidden": false, "presentable": false, "required": true, "system": false, + "cascadeDelete": true, "collectionId": "pbc_neuigkeiten", "maxSelect": 1, "minSelect": 0 + }, + { + "type": "relation", "id": "relation2001000211", "name": "user_id", + "help": "", "hidden": false, "presentable": false, "required": true, "system": false, + "cascadeDelete": true, "collectionId": "_pb_users_auth_", "maxSelect": 1, "minSelect": 0 + } + ], + "id": "pbc_reaktionen", + "indexes": ["CREATE UNIQUE INDEX idx_reaktion_unique ON reaktionen (beitrag_id, user_id)"], + "name": "reaktionen", + "system": false, + "type": "base" + }) + app.save(c) + } + +}, (app) => { + try { app.delete(app.findCollectionByNameOrId("pbc_reaktionen")) } catch(_) {} + try { app.delete(app.findCollectionByNameOrId("pbc_neuigkeiten")) } catch(_) {} +})