feat(backend): Wahl-Phasen, Verantwortliche-Invites, Auth fixes
- New VerantwortlicheInvite model: LT-issued invites so a person can register as Gemeinde Verantwortliche(r) for a specific Gemeinde, skipping the self-registration approval step. - Wahl/Workshop/Teilnehmer gain phase support (phasenAnzahl, beschreibung), mirroring the WP plugin's multi-phase elections. Teilnehmer unique constraint now scoped per phase. - Auth: team login + guest auth adjustments, spec coverage. - sync.service.ts: register VerantwortlicheInvite as a synced model. - wahl.service.ts: submitTeilnehmer updated for the new phase-scoped unique key. - client: login/home screen rework, new theme.dart, FCM web tweaks. - .gitignore: ignore .DS_Store. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import '../api.dart';
|
||||
import '../main.dart';
|
||||
import '../theme.dart';
|
||||
import 'admin_screen.dart';
|
||||
import 'chat_screen.dart';
|
||||
import 'files_screen.dart';
|
||||
@@ -109,8 +110,7 @@ class _IdentityCard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final lines = <String>[
|
||||
'Rolle: ${id.roleLabel}',
|
||||
if (id.email != null) 'E-Mail: ${id.email}',
|
||||
if (id.email != null) id.email!,
|
||||
if (id.isLeitungsteam)
|
||||
'Leitungsteam-Rechte gelten KC-übergreifend.'
|
||||
else if (id.memberships.length > 1)
|
||||
@@ -118,13 +118,35 @@ class _IdentityCard extends StatelessWidget {
|
||||
];
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Row(
|
||||
children: [
|
||||
Text('Angemeldet', style: Theme.of(context).textTheme.labelMedium),
|
||||
const SizedBox(height: 4),
|
||||
for (final l in lines) Text(l),
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [KcColors.blue, KcColors.teal],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.person, color: Colors.white),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(id.roleLabel, style: Theme.of(context).textTheme.titleMedium),
|
||||
for (final l in lines) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(l, style: Theme.of(context).textTheme.bodySmall),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -147,12 +169,38 @@ class _NavTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
leading: Icon(icon),
|
||||
title: Text(title),
|
||||
subtitle: Text(subtitle),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: KcColors.blue.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(icon, color: KcColors.blue),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 2),
|
||||
Text(subtitle, style: Theme.of(context).textTheme.bodySmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(Icons.chevron_right, color: KcColors.slate.withValues(alpha: 0.6)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user