Implementiert InfoDialog und ConfirmDialog in mehreren Komponenten, um die Benutzerinteraktion zu verbessern. Fügt Dialogzustände und Hilfsmethoden hinzu, die eine konsistente Handhabung von Informationen und Bestätigungen ermöglichen. Diese Änderungen erhöhen die Benutzerfreundlichkeit und verbessern die visuelle Rückmeldung in der Anwendung.
This commit is contained in:
@@ -120,6 +120,27 @@
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -128,13 +149,33 @@ import apiClient from './apiClient.js';
|
||||
import logoUrl from './assets/logo.png';
|
||||
import DialogManager from './components/DialogManager.vue';
|
||||
|
||||
import InfoDialog from './components/InfoDialog.vue';
|
||||
import ConfirmDialog from './components/ConfirmDialog.vue';
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
DialogManager
|
||||
},
|
||||
,
|
||||
InfoDialog,
|
||||
ConfirmDialog},
|
||||
data() {
|
||||
return {
|
||||
// Dialog States
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
},
|
||||
confirmDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
resolveCallback: null
|
||||
},
|
||||
selectedClub: null,
|
||||
sessionInterval: null,
|
||||
logoUrl,
|
||||
@@ -170,6 +211,38 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleConfirmResult(confirmed) {
|
||||
if (this.confirmDialog.resolveCallback) {
|
||||
this.confirmDialog.resolveCallback(confirmed);
|
||||
this.confirmDialog.resolveCallback = null;
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
|
||||
...mapActions(['setCurrentClub', 'setClubs', 'logout', 'toggleSidebar']),
|
||||
|
||||
async loadUserData() {
|
||||
|
||||
379
frontend/src/components/BaseDialog.vue
Normal file
379
frontend/src/components/BaseDialog.vue
Normal file
@@ -0,0 +1,379 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="isVisible"
|
||||
:class="['base-dialog', { 'modal-dialog': isModal, 'non-modal-dialog': !isModal }]"
|
||||
@click.self="handleOverlayClick"
|
||||
>
|
||||
<div
|
||||
:class="['dialog-container', sizeClass]"
|
||||
:style="dialogStyle"
|
||||
@mousedown="handleMouseDown"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div
|
||||
class="dialog-header"
|
||||
:class="{ draggable: isDraggable }"
|
||||
@mousedown="startDrag"
|
||||
>
|
||||
<h3 class="dialog-title">{{ title }}</h3>
|
||||
<slot name="header-actions"></slot>
|
||||
<div class="dialog-controls">
|
||||
<button
|
||||
v-if="minimizable"
|
||||
@click="$emit('minimize')"
|
||||
class="control-btn minimize-btn"
|
||||
title="Minimieren"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<button
|
||||
v-if="closable"
|
||||
@click="handleClose"
|
||||
class="control-btn close-btn"
|
||||
title="Schließen"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="dialog-body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<!-- Footer (optional) -->
|
||||
<div v-if="$slots.footer" class="dialog-footer">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseDialog',
|
||||
props: {
|
||||
// Sichtbarkeit
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
// Dialog-Typ
|
||||
isModal: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
||||
// Titel
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
|
||||
// Größe: 'small', 'medium', 'large', 'fullscreen'
|
||||
size: {
|
||||
type: String,
|
||||
default: 'medium',
|
||||
validator: (value) => ['small', 'medium', 'large', 'fullscreen'].includes(value)
|
||||
},
|
||||
|
||||
// Position für nicht-modale Dialoge
|
||||
position: {
|
||||
type: Object,
|
||||
default: () => ({ x: 100, y: 100 })
|
||||
},
|
||||
|
||||
// z-Index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 1000
|
||||
},
|
||||
|
||||
// Funktionalität
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
||||
minimizable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
||||
// Schließen bei Overlay-Klick (nur bei modalen Dialogen)
|
||||
closeOnOverlay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
localPosition: { ...this.position },
|
||||
isDragging: false,
|
||||
dragStartX: 0,
|
||||
dragStartY: 0
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
isVisible() {
|
||||
return this.modelValue;
|
||||
},
|
||||
|
||||
sizeClass() {
|
||||
return `dialog-${this.size}`;
|
||||
},
|
||||
|
||||
dialogStyle() {
|
||||
const style = {
|
||||
zIndex: this.zIndex
|
||||
};
|
||||
|
||||
if (!this.isModal) {
|
||||
style.left = `${this.localPosition.x}px`;
|
||||
style.top = `${this.localPosition.y}px`;
|
||||
}
|
||||
|
||||
return style;
|
||||
},
|
||||
|
||||
isDraggable() {
|
||||
return this.draggable && !this.isModal;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.$emit('update:modelValue', false);
|
||||
this.$emit('close');
|
||||
},
|
||||
|
||||
handleOverlayClick() {
|
||||
if (this.isModal && this.closeOnOverlay) {
|
||||
this.handleClose();
|
||||
}
|
||||
},
|
||||
|
||||
handleMouseDown() {
|
||||
if (!this.isModal) {
|
||||
this.$emit('focus');
|
||||
}
|
||||
},
|
||||
|
||||
startDrag(event) {
|
||||
if (!this.isDraggable) return;
|
||||
|
||||
this.isDragging = true;
|
||||
this.dragStartX = event.clientX - this.localPosition.x;
|
||||
this.dragStartY = event.clientY - this.localPosition.y;
|
||||
|
||||
document.addEventListener('mousemove', this.onDrag);
|
||||
document.addEventListener('mouseup', this.stopDrag);
|
||||
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
onDrag(event) {
|
||||
if (!this.isDragging) return;
|
||||
|
||||
this.localPosition.x = event.clientX - this.dragStartX;
|
||||
this.localPosition.y = event.clientY - this.dragStartY;
|
||||
|
||||
this.$emit('update:position', { ...this.localPosition });
|
||||
},
|
||||
|
||||
stopDrag() {
|
||||
this.isDragging = false;
|
||||
document.removeEventListener('mousemove', this.onDrag);
|
||||
document.removeEventListener('mouseup', this.stopDrag);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
position: {
|
||||
handler(newPos) {
|
||||
this.localPosition = { ...newPos };
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
if (this.isDragging) {
|
||||
this.stopDrag();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Base Dialog Wrapper */
|
||||
.base-dialog {
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Modal Dialog (mit Overlay) */
|
||||
.modal-dialog {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* Non-Modal Dialog (ohne Overlay) */
|
||||
.non-modal-dialog {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Dialog Container */
|
||||
.dialog-container {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
||||
pointer-events: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
/* Modal Dialog Container */
|
||||
.modal-dialog .dialog-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Non-Modal Dialog Container */
|
||||
.non-modal-dialog .dialog-container {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* Größen */
|
||||
.dialog-small {
|
||||
width: 400px;
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
.dialog-medium {
|
||||
width: 600px;
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
.dialog-large {
|
||||
width: 900px;
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
.dialog-fullscreen {
|
||||
width: 90vw;
|
||||
height: 90vh;
|
||||
max-width: none;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.dialog-header {
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
|
||||
color: white;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dialog-header.draggable {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Controls */
|
||||
.dialog-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: #dc3545;
|
||||
}
|
||||
|
||||
/* Body */
|
||||
.dialog-body {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.dialog-footer {
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.dialog-small,
|
||||
.dialog-medium,
|
||||
.dialog-large {
|
||||
width: 95vw;
|
||||
max-width: 95vw;
|
||||
}
|
||||
|
||||
.dialog-fullscreen {
|
||||
width: 95vw;
|
||||
height: 95vh;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
209
frontend/src/components/ConfirmDialog.vue
Normal file
209
frontend/src/components/ConfirmDialog.vue
Normal file
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<BaseDialog
|
||||
:model-value="modelValue"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
:title="title"
|
||||
:is-modal="true"
|
||||
size="small"
|
||||
:closable="true"
|
||||
:close-on-overlay="false"
|
||||
@close="handleCancel"
|
||||
>
|
||||
<!-- Content -->
|
||||
<div class="confirm-content">
|
||||
<div v-if="icon" class="confirm-icon" :class="`icon-${type}`">
|
||||
{{ icon }}
|
||||
</div>
|
||||
<div class="confirm-message">
|
||||
{{ message }}
|
||||
</div>
|
||||
<div v-if="details" class="confirm-details">
|
||||
{{ details }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer mit Aktionen -->
|
||||
<template #footer>
|
||||
<button
|
||||
v-if="showCancel"
|
||||
@click="handleCancel"
|
||||
class="btn-secondary"
|
||||
>
|
||||
{{ cancelText }}
|
||||
</button>
|
||||
<button
|
||||
@click="handleConfirm"
|
||||
:class="confirmButtonClass"
|
||||
>
|
||||
{{ confirmText }}
|
||||
</button>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseDialog from './BaseDialog.vue';
|
||||
|
||||
export default {
|
||||
name: 'ConfirmDialog',
|
||||
components: {
|
||||
BaseDialog
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Bestätigung'
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
details: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'info',
|
||||
validator: (value) => ['info', 'warning', 'danger', 'success'].includes(value)
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: 'OK'
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: 'Abbrechen'
|
||||
},
|
||||
showCancel: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
icon() {
|
||||
const icons = {
|
||||
info: 'ℹ️',
|
||||
warning: '⚠️',
|
||||
danger: '⛔',
|
||||
success: '✅'
|
||||
};
|
||||
return icons[this.type] || icons.info;
|
||||
},
|
||||
confirmButtonClass() {
|
||||
const classes = {
|
||||
info: 'btn-primary',
|
||||
warning: 'btn-warning',
|
||||
danger: 'btn-danger',
|
||||
success: 'btn-primary'
|
||||
};
|
||||
return classes[this.type] || 'btn-primary';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleConfirm() {
|
||||
this.$emit('confirm');
|
||||
this.$emit('update:modelValue', false);
|
||||
},
|
||||
handleCancel() {
|
||||
this.$emit('cancel');
|
||||
this.$emit('update:modelValue', false);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.confirm-content {
|
||||
text-align: center;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.confirm-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.icon-info {
|
||||
color: #17a2b8;
|
||||
}
|
||||
|
||||
.icon-warning {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.icon-danger {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.icon-success {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.confirm-message {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.confirm-details {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-muted);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-secondary,
|
||||
.btn-primary,
|
||||
.btn-warning,
|
||||
.btn-danger {
|
||||
padding: 0.5rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-warning:hover {
|
||||
background: #e0a800;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
</style>
|
||||
|
||||
414
frontend/src/components/DIALOG_TEMPLATES.md
Normal file
414
frontend/src/components/DIALOG_TEMPLATES.md
Normal file
@@ -0,0 +1,414 @@
|
||||
# Dialog-Templates Dokumentation
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Dialog-System bietet wiederverwendbare Templates für modale und nicht-modale Dialoge.
|
||||
|
||||
## Komponenten
|
||||
|
||||
### 1. BaseDialog.vue
|
||||
|
||||
Die Basis-Komponente für alle Dialoge. Unterstützt sowohl modale als auch nicht-modale Dialoge.
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Typ | Default | Beschreibung |
|
||||
|------|-----|---------|--------------|
|
||||
| `modelValue` | Boolean | `false` | v-model Binding für Sichtbarkeit |
|
||||
| `isModal` | Boolean | `true` | Modaler Dialog (mit Overlay) oder nicht-modal |
|
||||
| `title` | String | - | Dialog-Titel (erforderlich) |
|
||||
| `size` | String | `'medium'` | Größe: `'small'`, `'medium'`, `'large'`, `'fullscreen'` |
|
||||
| `position` | Object | `{ x: 100, y: 100 }` | Position für nicht-modale Dialoge |
|
||||
| `zIndex` | Number | `1000` | z-Index des Dialogs |
|
||||
| `closable` | Boolean | `true` | Schließen-Button anzeigen |
|
||||
| `minimizable` | Boolean | `false` | Minimieren-Button anzeigen |
|
||||
| `draggable` | Boolean | `true` | Dialog verschiebbar (nur nicht-modal) |
|
||||
| `closeOnOverlay` | Boolean | `true` | Bei Klick auf Overlay schließen (nur modal) |
|
||||
|
||||
#### Events
|
||||
|
||||
| Event | Parameter | Beschreibung |
|
||||
|-------|-----------|--------------|
|
||||
| `update:modelValue` | Boolean | Wird beim Öffnen/Schließen gefeuert |
|
||||
| `close` | - | Wird beim Schließen gefeuert |
|
||||
| `minimize` | - | Wird beim Minimieren gefeuert |
|
||||
| `focus` | - | Wird bei Klick auf nicht-modalen Dialog gefeuert |
|
||||
| `update:position` | Object | Neue Position nach Verschieben |
|
||||
|
||||
#### Slots
|
||||
|
||||
| Slot | Beschreibung |
|
||||
|------|--------------|
|
||||
| `default` | Dialog-Inhalt |
|
||||
| `header-actions` | Zusätzliche Aktionen im Header |
|
||||
| `footer` | Dialog-Footer (optional) |
|
||||
|
||||
#### Beispiel: Modaler Dialog
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<BaseDialog
|
||||
v-model="isOpen"
|
||||
title="Mein Dialog"
|
||||
size="medium"
|
||||
@close="handleClose"
|
||||
>
|
||||
<p>Dialog-Inhalt hier</p>
|
||||
|
||||
<template #footer>
|
||||
<button @click="isOpen = false">Schließen</button>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import BaseDialog from '@/components/BaseDialog.vue';
|
||||
|
||||
export default {
|
||||
components: { BaseDialog },
|
||||
setup() {
|
||||
const isOpen = ref(false);
|
||||
|
||||
const handleClose = () => {
|
||||
console.log('Dialog geschlossen');
|
||||
};
|
||||
|
||||
return { isOpen, handleClose };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
#### Beispiel: Nicht-modaler Dialog
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<BaseDialog
|
||||
v-model="isOpen"
|
||||
title="Nicht-modaler Dialog"
|
||||
:is-modal="false"
|
||||
:position="position"
|
||||
@update:position="position = $event"
|
||||
:draggable="true"
|
||||
:minimizable="true"
|
||||
size="medium"
|
||||
>
|
||||
<p>Dieser Dialog kann verschoben werden!</p>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive } from 'vue';
|
||||
import BaseDialog from '@/components/BaseDialog.vue';
|
||||
|
||||
export default {
|
||||
components: { BaseDialog },
|
||||
setup() {
|
||||
const isOpen = ref(false);
|
||||
const position = reactive({ x: 100, y: 100 });
|
||||
|
||||
return { isOpen, position };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## 2. ConfirmDialog.vue
|
||||
|
||||
Spezialisierter Dialog für Bestätigungen.
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Typ | Default | Beschreibung |
|
||||
|------|-----|---------|--------------|
|
||||
| `modelValue` | Boolean | `false` | v-model Binding |
|
||||
| `title` | String | `'Bestätigung'` | Dialog-Titel |
|
||||
| `message` | String | - | Hauptnachricht (erforderlich) |
|
||||
| `details` | String | `''` | Zusätzliche Details |
|
||||
| `type` | String | `'info'` | Typ: `'info'`, `'warning'`, `'danger'`, `'success'` |
|
||||
| `confirmText` | String | `'OK'` | Text für Bestätigen-Button |
|
||||
| `cancelText` | String | `'Abbrechen'` | Text für Abbrechen-Button |
|
||||
| `showCancel` | Boolean | `true` | Abbrechen-Button anzeigen |
|
||||
|
||||
#### Events
|
||||
|
||||
| Event | Beschreibung |
|
||||
|-------|--------------|
|
||||
| `confirm` | Wird bei Bestätigung gefeuert |
|
||||
| `cancel` | Wird bei Abbruch gefeuert |
|
||||
|
||||
#### Beispiel
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ConfirmDialog
|
||||
v-model="showConfirm"
|
||||
title="Löschen bestätigen"
|
||||
message="Möchten Sie diesen Eintrag wirklich löschen?"
|
||||
type="danger"
|
||||
confirm-text="Löschen"
|
||||
@confirm="handleDelete"
|
||||
@cancel="handleCancel"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import ConfirmDialog from '@/components/ConfirmDialog.vue';
|
||||
|
||||
export default {
|
||||
components: { ConfirmDialog },
|
||||
setup() {
|
||||
const showConfirm = ref(false);
|
||||
|
||||
const handleDelete = () => {
|
||||
console.log('Gelöscht');
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
console.log('Abgebrochen');
|
||||
};
|
||||
|
||||
return { showConfirm, handleDelete, handleCancel };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## 3. ImageDialog.vue
|
||||
|
||||
Einfacher Dialog zur Anzeige von Bildern.
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Typ | Default | Beschreibung |
|
||||
|------|-----|---------|--------------|
|
||||
| `modelValue` | Boolean | `false` | v-model Binding |
|
||||
| `title` | String | `'Bild'` | Dialog-Titel |
|
||||
| `imageUrl` | String | `''` | URL des anzuzeigenden Bildes |
|
||||
|
||||
#### Events
|
||||
|
||||
| Event | Beschreibung |
|
||||
|-------|--------------|
|
||||
| `close` | Wird beim Schließen gefeuert |
|
||||
|
||||
#### Beispiel
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ImageDialog
|
||||
v-model="showImage"
|
||||
title="Aktivitätsbild"
|
||||
:image-url="imageUrl"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import ImageDialog from '@/components/ImageDialog.vue';
|
||||
|
||||
export default {
|
||||
components: { ImageDialog },
|
||||
setup() {
|
||||
const showImage = ref(false);
|
||||
const imageUrl = ref('');
|
||||
|
||||
return { showImage, imageUrl };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## 4. ImageViewerDialog.vue
|
||||
|
||||
Erweiterter Bild-Dialog mit Aktionen (Drehen, Zoom).
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Typ | Default | Beschreibung |
|
||||
|------|-----|---------|--------------|
|
||||
| `modelValue` | Boolean | `false` | v-model Binding |
|
||||
| `title` | String | `'Bild'` | Dialog-Titel |
|
||||
| `imageUrl` | String | `''` | URL des anzuzeigenden Bildes |
|
||||
| `memberId` | Number/String | `null` | ID des zugehörigen Members (optional) |
|
||||
| `showActions` | Boolean | `true` | Aktions-Buttons anzeigen |
|
||||
| `allowRotate` | Boolean | `true` | Drehen-Buttons anzeigen |
|
||||
| `allowZoom` | Boolean | `false` | Zoom-Button anzeigen |
|
||||
|
||||
#### Events
|
||||
|
||||
| Event | Parameter | Beschreibung |
|
||||
|-------|-----------|--------------|
|
||||
| `close` | - | Wird beim Schließen gefeuert |
|
||||
| `rotate` | Object | Wird beim Drehen gefeuert: `{ direction, memberId, rotation }` |
|
||||
|
||||
#### Beispiel
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ImageViewerDialog
|
||||
v-model="showImageModal"
|
||||
title="Mitgliedsbild"
|
||||
:image-url="selectedImageUrl"
|
||||
:member-id="selectedMemberId"
|
||||
:allow-rotate="true"
|
||||
@rotate="handleRotate"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import ImageViewerDialog from '@/components/ImageViewerDialog.vue';
|
||||
import apiClient from '@/apiClient.js';
|
||||
|
||||
export default {
|
||||
components: { ImageViewerDialog },
|
||||
setup() {
|
||||
const showImageModal = ref(false);
|
||||
const selectedImageUrl = ref('');
|
||||
const selectedMemberId = ref(null);
|
||||
|
||||
const handleRotate = async (event) => {
|
||||
const { direction, memberId } = event;
|
||||
// API-Aufruf zum Drehen des Bildes
|
||||
await apiClient.post(`/members/${memberId}/rotate`, { direction });
|
||||
};
|
||||
|
||||
return { showImageModal, selectedImageUrl, selectedMemberId, handleRotate };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## 5. Composables
|
||||
|
||||
### useDialog()
|
||||
|
||||
Einfaches Composable für Dialog-Verwaltung.
|
||||
|
||||
```javascript
|
||||
import { useDialog } from '@/composables/useDialog.js';
|
||||
|
||||
const { isOpen, open, close, toggle } = useDialog();
|
||||
|
||||
// Dialog öffnen
|
||||
open();
|
||||
|
||||
// Dialog schließen
|
||||
close();
|
||||
|
||||
// Dialog umschalten
|
||||
toggle();
|
||||
```
|
||||
|
||||
## useConfirm()
|
||||
|
||||
Promise-basiertes Composable für Bestätigungsdialoge.
|
||||
|
||||
```javascript
|
||||
import { useConfirm } from '@/composables/useDialog.js';
|
||||
|
||||
const { isOpen, config, confirm, handleConfirm, handleCancel } = useConfirm();
|
||||
|
||||
// Im Template:
|
||||
<ConfirmDialog
|
||||
v-model="isOpen"
|
||||
:title="config.title"
|
||||
:message="config.message"
|
||||
:type="config.type"
|
||||
@confirm="handleConfirm"
|
||||
@cancel="handleCancel"
|
||||
/>
|
||||
|
||||
// In Methoden:
|
||||
async function deleteItem() {
|
||||
const confirmed = await confirm({
|
||||
title: 'Löschen bestätigen',
|
||||
message: 'Wirklich löschen?',
|
||||
type: 'danger'
|
||||
});
|
||||
|
||||
if (confirmed) {
|
||||
// Löschvorgang durchführen
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dialog-Größen
|
||||
|
||||
| Größe | Breite | Beschreibung |
|
||||
|-------|--------|--------------|
|
||||
| `small` | 400px | Kleine Dialoge (z.B. Bestätigungen) |
|
||||
| `medium` | 600px | Standard-Dialoge |
|
||||
| `large` | 900px | Große Dialoge mit viel Inhalt |
|
||||
| `fullscreen` | 90vw x 90vh | Fast Fullscreen |
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Modale Dialoge verwenden für:
|
||||
- Wichtige Benutzer-Entscheidungen
|
||||
- Formulare, die Fokus erfordern
|
||||
- Warnungen und Fehler
|
||||
- Prozesse, die nicht unterbrochen werden sollten
|
||||
|
||||
### Nicht-modale Dialoge verwenden für:
|
||||
- Zusätzliche Informationen
|
||||
- Tools und Paletten
|
||||
- Mehrere gleichzeitige Arbeitsschritte
|
||||
- Drag & Drop-Workflows
|
||||
|
||||
### Tipps
|
||||
|
||||
1. **Minimieren-Funktion**: Nur bei nicht-modalen Dialogen sinnvoll
|
||||
2. **closeOnOverlay**: Bei wichtigen Formularen auf `false` setzen
|
||||
3. **z-Index**: Bei mehreren nicht-modalen Dialogen unterschiedliche z-Indices verwenden
|
||||
4. **Footer-Slot**: Für Aktions-Buttons verwenden
|
||||
5. **header-actions**: Für kontextspezifische Header-Aktionen
|
||||
|
||||
## Styling
|
||||
|
||||
Die Dialoge verwenden CSS-Variablen für konsistentes Styling:
|
||||
|
||||
- `--primary-color`: Primärfarbe für Header und Buttons
|
||||
- `--primary-hover`: Hover-Farbe
|
||||
- `--border-color`: Rahmenfarbe
|
||||
- `--text-color`: Textfarbe
|
||||
- `--text-muted`: Gedämpfte Textfarbe
|
||||
|
||||
## Migration bestehender Dialoge
|
||||
|
||||
### Alt (individueller Dialog):
|
||||
```vue
|
||||
<div v-if="showDialog" class="modal-overlay" @click.self="closeDialog">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h3>Titel</h3>
|
||||
<button @click="closeDialog">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Inhalt
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Neu (BaseDialog):
|
||||
```vue
|
||||
<BaseDialog v-model="showDialog" title="Titel">
|
||||
Inhalt
|
||||
</BaseDialog>
|
||||
```
|
||||
|
||||
## Beispiele ansehen
|
||||
|
||||
Die Datei `DialogExamples.vue` enthält vollständige Beispiele für alle Dialog-Typen.
|
||||
|
||||
Route hinzufügen in `router.js`:
|
||||
```javascript
|
||||
import DialogExamples from './components/DialogExamples.vue';
|
||||
|
||||
{ path: '/dialog-examples', component: DialogExamples }
|
||||
```
|
||||
|
||||
469
frontend/src/components/DialogExamples.vue
Normal file
469
frontend/src/components/DialogExamples.vue
Normal file
@@ -0,0 +1,469 @@
|
||||
<template>
|
||||
<div class="dialog-examples">
|
||||
<h2>Dialog-Beispiele</h2>
|
||||
|
||||
<div class="example-section">
|
||||
<h3>Modale Dialoge</h3>
|
||||
<div class="button-group">
|
||||
<button @click="openSimpleModal" class="btn-primary">Einfacher Modal</button>
|
||||
<button @click="openLargeModal" class="btn-primary">Großer Modal</button>
|
||||
<button @click="openFullscreenModal" class="btn-primary">Fullscreen Modal</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="example-section">
|
||||
<h3>Nicht-modale Dialoge</h3>
|
||||
<div class="button-group">
|
||||
<button @click="openNonModalDialog" class="btn-primary">Nicht-modaler Dialog</button>
|
||||
<button @click="openMultipleDialogs" class="btn-primary">Mehrere Dialoge</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="example-section">
|
||||
<h3>Informations-Dialoge</h3>
|
||||
<div class="button-group">
|
||||
<button @click="showInfoDialog" class="btn-primary">Info</button>
|
||||
<button @click="showSuccessDialog" class="btn-success">Erfolg</button>
|
||||
<button @click="showWarningDialog" class="btn-warning">Warnung</button>
|
||||
<button @click="showErrorDialog" class="btn-danger">Fehler</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="example-section">
|
||||
<h3>Bestätigungs-Dialoge</h3>
|
||||
<div class="button-group">
|
||||
<button @click="showInfoConfirm" class="btn-primary">Info</button>
|
||||
<button @click="showWarningConfirm" class="btn-warning">Warnung</button>
|
||||
<button @click="showDangerConfirm" class="btn-danger">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="example-section">
|
||||
<h3>Composable-Verwendung</h3>
|
||||
<div class="button-group">
|
||||
<button @click="composableDialog.open()" class="btn-primary">useDialog Beispiel</button>
|
||||
<button @click="showComposableConfirm" class="btn-primary">useConfirm Beispiel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Einfacher Modal Dialog -->
|
||||
<BaseDialog
|
||||
v-model="simpleModal.isOpen"
|
||||
title="Einfacher Modal Dialog"
|
||||
size="medium"
|
||||
>
|
||||
<p>Dies ist ein einfacher modaler Dialog mit mittlerer Größe.</p>
|
||||
<p>Klicken Sie außerhalb oder auf das X, um zu schließen.</p>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Großer Modal Dialog -->
|
||||
<BaseDialog
|
||||
v-model="largeModal.isOpen"
|
||||
title="Großer Modal Dialog"
|
||||
size="large"
|
||||
>
|
||||
<p>Dies ist ein großer modaler Dialog.</p>
|
||||
<p>Er bietet mehr Platz für Inhalte.</p>
|
||||
<div style="height: 400px; background: #f5f5f5; margin-top: 1rem; padding: 1rem;">
|
||||
Scroll-Bereich für viel Inhalt...
|
||||
</div>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Fullscreen Modal Dialog -->
|
||||
<BaseDialog
|
||||
v-model="fullscreenModal.isOpen"
|
||||
title="Fullscreen Modal Dialog"
|
||||
size="fullscreen"
|
||||
>
|
||||
<p>Dies ist ein Fullscreen-Dialog.</p>
|
||||
<p>Er nimmt fast den gesamten Bildschirm ein (90vw x 90vh).</p>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Nicht-modaler Dialog -->
|
||||
<BaseDialog
|
||||
v-model="nonModal.isOpen"
|
||||
title="Nicht-modaler Dialog"
|
||||
:is-modal="false"
|
||||
:position="nonModal.position"
|
||||
@update:position="nonModal.position = $event"
|
||||
size="medium"
|
||||
:draggable="true"
|
||||
:minimizable="true"
|
||||
@minimize="handleMinimize('nonModal')"
|
||||
>
|
||||
<p>Dies ist ein nicht-modaler Dialog.</p>
|
||||
<p>Sie können ihn verschieben und mehrere gleichzeitig öffnen!</p>
|
||||
<template #footer>
|
||||
<button @click="nonModal.isOpen = false" class="btn-secondary">Schließen</button>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Zweiter nicht-modaler Dialog -->
|
||||
<BaseDialog
|
||||
v-model="nonModal2.isOpen"
|
||||
title="Zweiter nicht-modaler Dialog"
|
||||
:is-modal="false"
|
||||
:position="nonModal2.position"
|
||||
@update:position="nonModal2.position = $event"
|
||||
size="small"
|
||||
:draggable="true"
|
||||
:z-index="1001"
|
||||
>
|
||||
<p>Noch ein nicht-modaler Dialog!</p>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Informations-Dialoge -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
title="Information"
|
||||
message="Dies ist eine Informationsmeldung."
|
||||
type="info"
|
||||
@ok="infoDialog.isOpen = false"
|
||||
/>
|
||||
|
||||
<InfoDialog
|
||||
v-model="successDialog.isOpen"
|
||||
title="Erfolg"
|
||||
message="Der Vorgang wurde erfolgreich abgeschlossen!"
|
||||
details="Alle Änderungen wurden gespeichert."
|
||||
type="success"
|
||||
@ok="successDialog.isOpen = false"
|
||||
/>
|
||||
|
||||
<InfoDialog
|
||||
v-model="warningDialog.isOpen"
|
||||
title="Warnung"
|
||||
message="Bitte beachten Sie folgende Hinweise."
|
||||
details="Einige Felder sind möglicherweise nicht vollständig ausgefüllt."
|
||||
type="warning"
|
||||
@ok="warningDialog.isOpen = false"
|
||||
/>
|
||||
|
||||
<InfoDialog
|
||||
v-model="errorDialog.isOpen"
|
||||
title="Fehler"
|
||||
message="Ein Fehler ist aufgetreten."
|
||||
details="Bitte versuchen Sie es später erneut."
|
||||
type="error"
|
||||
@ok="errorDialog.isOpen = false"
|
||||
/>
|
||||
|
||||
<!-- Bestätigungs-Dialoge -->
|
||||
<ConfirmDialog
|
||||
v-model="infoConfirm.isOpen"
|
||||
title="Information"
|
||||
message="Dies ist eine Informationsmeldung."
|
||||
type="info"
|
||||
:show-cancel="false"
|
||||
@confirm="infoConfirm.isOpen = false"
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
v-model="warningConfirm.isOpen"
|
||||
title="Warnung"
|
||||
message="Sind Sie sicher, dass Sie fortfahren möchten?"
|
||||
details="Diese Aktion kann nicht rückgängig gemacht werden."
|
||||
type="warning"
|
||||
@confirm="handleWarningConfirm"
|
||||
@cancel="warningConfirm.isOpen = false"
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
v-model="dangerConfirm.isOpen"
|
||||
title="Löschen bestätigen"
|
||||
message="Möchten Sie diesen Eintrag wirklich löschen?"
|
||||
type="danger"
|
||||
confirm-text="Löschen"
|
||||
@confirm="handleDelete"
|
||||
@cancel="dangerConfirm.isOpen = false"
|
||||
/>
|
||||
|
||||
<!-- Composable Dialog -->
|
||||
<BaseDialog
|
||||
v-model="composableDialog.isOpen"
|
||||
title="Dialog mit useDialog Composable"
|
||||
size="medium"
|
||||
>
|
||||
<p>Dieser Dialog verwendet das useDialog Composable.</p>
|
||||
<p>Das macht die Verwaltung einfacher!</p>
|
||||
<template #footer>
|
||||
<button @click="composableDialog.close()" class="btn-primary">Schließen</button>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Composable Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmComposable.isOpen"
|
||||
:title="confirmComposable.config.title"
|
||||
:message="confirmComposable.config.message"
|
||||
:type="confirmComposable.config.type"
|
||||
@confirm="confirmComposable.handleConfirm"
|
||||
@cancel="confirmComposable.handleCancel"
|
||||
/>
|
||||
|
||||
<!-- Minimierte Dialoge Anzeige -->
|
||||
<div v-if="minimizedDialogs.length > 0" class="minimized-section">
|
||||
<h4>Minimierte Dialoge:</h4>
|
||||
<button
|
||||
v-for="(dialog, index) in minimizedDialogs"
|
||||
:key="index"
|
||||
@click="restoreDialog(dialog)"
|
||||
class="minimized-btn"
|
||||
>
|
||||
{{ dialog }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref } from 'vue';
|
||||
import BaseDialog from './BaseDialog.vue';
|
||||
import ConfirmDialog from './ConfirmDialog.vue';
|
||||
import InfoDialog from './InfoDialog.vue';
|
||||
import { useDialog, useConfirm } from '../composables/useDialog.js';
|
||||
|
||||
export default {
|
||||
name: 'DialogExamples',
|
||||
components: {
|
||||
BaseDialog,
|
||||
ConfirmDialog,
|
||||
InfoDialog
|
||||
},
|
||||
setup() {
|
||||
// Modale Dialoge
|
||||
const simpleModal = reactive({ isOpen: false });
|
||||
const largeModal = reactive({ isOpen: false });
|
||||
const fullscreenModal = reactive({ isOpen: false });
|
||||
|
||||
// Nicht-modale Dialoge
|
||||
const nonModal = reactive({
|
||||
isOpen: false,
|
||||
position: { x: 100, y: 100 }
|
||||
});
|
||||
|
||||
const nonModal2 = reactive({
|
||||
isOpen: false,
|
||||
position: { x: 600, y: 150 }
|
||||
});
|
||||
|
||||
// Informations-Dialoge
|
||||
const infoDialog = reactive({ isOpen: false });
|
||||
const successDialog = reactive({ isOpen: false });
|
||||
const warningDialog = reactive({ isOpen: false });
|
||||
const errorDialog = reactive({ isOpen: false });
|
||||
|
||||
// Bestätigungs-Dialoge
|
||||
const infoConfirm = reactive({ isOpen: false });
|
||||
const warningConfirm = reactive({ isOpen: false });
|
||||
const dangerConfirm = reactive({ isOpen: false });
|
||||
|
||||
// Composables
|
||||
const composableDialog = useDialog();
|
||||
const confirmComposable = useConfirm();
|
||||
|
||||
// Minimierte Dialoge
|
||||
const minimizedDialogs = ref([]);
|
||||
|
||||
return {
|
||||
simpleModal,
|
||||
largeModal,
|
||||
fullscreenModal,
|
||||
nonModal,
|
||||
nonModal2,
|
||||
infoDialog,
|
||||
successDialog,
|
||||
warningDialog,
|
||||
errorDialog,
|
||||
infoConfirm,
|
||||
warningConfirm,
|
||||
dangerConfirm,
|
||||
composableDialog,
|
||||
confirmComposable,
|
||||
minimizedDialogs
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
openSimpleModal() {
|
||||
this.simpleModal.isOpen = true;
|
||||
},
|
||||
|
||||
openLargeModal() {
|
||||
this.largeModal.isOpen = true;
|
||||
},
|
||||
|
||||
openFullscreenModal() {
|
||||
this.fullscreenModal.isOpen = true;
|
||||
},
|
||||
|
||||
openNonModalDialog() {
|
||||
this.nonModal.isOpen = true;
|
||||
},
|
||||
|
||||
openMultipleDialogs() {
|
||||
this.nonModal.isOpen = true;
|
||||
this.nonModal2.isOpen = true;
|
||||
},
|
||||
|
||||
showInfoConfirm() {
|
||||
this.infoConfirm.isOpen = true;
|
||||
},
|
||||
|
||||
showWarningConfirm() {
|
||||
this.warningConfirm.isOpen = true;
|
||||
},
|
||||
|
||||
showDangerConfirm() {
|
||||
this.dangerConfirm.isOpen = true;
|
||||
},
|
||||
|
||||
async showComposableConfirm() {
|
||||
const result = await this.confirmComposable.confirm({
|
||||
title: 'useConfirm Beispiel',
|
||||
message: 'Möchten Sie fortfahren?',
|
||||
details: 'Dies ist ein Beispiel für das useConfirm Composable.',
|
||||
type: 'info'
|
||||
});
|
||||
|
||||
if (result) {
|
||||
alert('Bestätigt!');
|
||||
} else {
|
||||
alert('Abgebrochen!');
|
||||
}
|
||||
},
|
||||
|
||||
handleWarningConfirm() {
|
||||
console.log('Warnung bestätigt');
|
||||
this.warningConfirm.isOpen = false;
|
||||
},
|
||||
|
||||
handleDelete() {
|
||||
console.log('Eintrag gelöscht');
|
||||
this.dangerConfirm.isOpen = false;
|
||||
},
|
||||
|
||||
handleMinimize(dialogName) {
|
||||
this.minimizedDialogs.push(dialogName);
|
||||
this[dialogName].isOpen = false;
|
||||
},
|
||||
|
||||
restoreDialog(dialogName) {
|
||||
this[dialogName].isOpen = true;
|
||||
const index = this.minimizedDialogs.indexOf(dialogName);
|
||||
if (index > -1) {
|
||||
this.minimizedDialogs.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-examples {
|
||||
padding: 2rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.example-section {
|
||||
margin-bottom: 2rem;
|
||||
padding: 1rem;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.example-section h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn-primary,
|
||||
.btn-secondary,
|
||||
.btn-warning,
|
||||
.btn-danger {
|
||||
padding: 0.5rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-warning:hover {
|
||||
background: #e0a800;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.minimized-section {
|
||||
position: fixed;
|
||||
bottom: 2rem;
|
||||
right: 2rem;
|
||||
background: white;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.minimized-section h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.minimized-btn {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.minimized-btn:hover {
|
||||
background: var(--primary-hover);
|
||||
}
|
||||
</style>
|
||||
|
||||
125
frontend/src/components/ImageDialog.vue
Normal file
125
frontend/src/components/ImageDialog.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<BaseDialog
|
||||
:model-value="modelValue"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
:title="title"
|
||||
:is-modal="true"
|
||||
size="large"
|
||||
:closable="true"
|
||||
:close-on-overlay="true"
|
||||
@close="handleClose"
|
||||
>
|
||||
<!-- Image Content -->
|
||||
<div class="image-dialog-content">
|
||||
<img
|
||||
v-if="imageUrl"
|
||||
:src="imageUrl"
|
||||
:alt="title"
|
||||
class="dialog-image"
|
||||
/>
|
||||
<div v-else class="no-image">
|
||||
Kein Bild verfügbar
|
||||
</div>
|
||||
|
||||
<!-- Optionale zusätzliche Inhalte -->
|
||||
<div v-if="$slots.default" class="image-extra-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer mit optionalen Aktionen -->
|
||||
<template #footer>
|
||||
<slot name="actions">
|
||||
<button @click="handleClose" class="btn-secondary">
|
||||
Schließen
|
||||
</button>
|
||||
</slot>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseDialog from './BaseDialog.vue';
|
||||
|
||||
export default {
|
||||
name: 'ImageDialog',
|
||||
components: {
|
||||
BaseDialog
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Bild'
|
||||
},
|
||||
imageUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'close'],
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.$emit('update:modelValue', false);
|
||||
this.$emit('close');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-dialog-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.dialog-image {
|
||||
max-width: 100%;
|
||||
max-height: 70vh;
|
||||
object-fit: contain;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.no-image {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.image-extra-content {
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
padding: 0.5rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.dialog-image {
|
||||
max-height: 60vh;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
259
frontend/src/components/ImageViewerDialog.vue
Normal file
259
frontend/src/components/ImageViewerDialog.vue
Normal file
@@ -0,0 +1,259 @@
|
||||
<template>
|
||||
<BaseDialog
|
||||
:model-value="modelValue"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
:title="title"
|
||||
:is-modal="true"
|
||||
size="large"
|
||||
:closable="true"
|
||||
:close-on-overlay="true"
|
||||
@close="handleClose"
|
||||
>
|
||||
<!-- Image Content -->
|
||||
<div class="image-viewer-content">
|
||||
<div class="image-container">
|
||||
<img
|
||||
v-if="imageUrl"
|
||||
:src="imageUrl"
|
||||
:alt="title"
|
||||
class="viewer-image"
|
||||
:style="imageStyle"
|
||||
/>
|
||||
<div v-else class="no-image">
|
||||
Kein Bild verfügbar
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bild-Aktionen -->
|
||||
<div v-if="showActions && imageUrl" class="image-actions">
|
||||
<button
|
||||
v-if="allowRotate"
|
||||
@click="rotateLeft"
|
||||
class="action-btn"
|
||||
title="90° links drehen"
|
||||
>
|
||||
↺ Links drehen
|
||||
</button>
|
||||
<button
|
||||
v-if="allowRotate"
|
||||
@click="rotateRight"
|
||||
class="action-btn"
|
||||
title="90° rechts drehen"
|
||||
>
|
||||
↻ Rechts drehen
|
||||
</button>
|
||||
<button
|
||||
v-if="allowZoom"
|
||||
@click="resetZoom"
|
||||
class="action-btn"
|
||||
title="Zoom zurücksetzen"
|
||||
>
|
||||
🔍 Zoom zurücksetzen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Zusätzliche Inhalte -->
|
||||
<div v-if="$slots.default" class="extra-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<template #footer>
|
||||
<slot name="footer">
|
||||
<button @click="handleClose" class="btn-secondary">
|
||||
Schließen
|
||||
</button>
|
||||
</slot>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseDialog from './BaseDialog.vue';
|
||||
|
||||
export default {
|
||||
name: 'ImageViewerDialog',
|
||||
components: {
|
||||
BaseDialog
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Bild'
|
||||
},
|
||||
imageUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
memberId: {
|
||||
type: [Number, String],
|
||||
default: null
|
||||
},
|
||||
showActions: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
allowRotate: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
allowZoom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'close', 'rotate'],
|
||||
data() {
|
||||
return {
|
||||
scale: 1
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
imageStyle() {
|
||||
return {
|
||||
transform: `scale(${this.scale})`
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.scale = 1;
|
||||
this.$emit('update:modelValue', false);
|
||||
this.$emit('close');
|
||||
},
|
||||
|
||||
rotateLeft() {
|
||||
// Emit rotate event - das Bild wird auf dem Server gedreht
|
||||
// und dann neu geladen, daher keine lokale Rotation
|
||||
this.$emit('rotate', {
|
||||
direction: 'left',
|
||||
memberId: this.memberId
|
||||
});
|
||||
},
|
||||
|
||||
rotateRight() {
|
||||
// Emit rotate event - das Bild wird auf dem Server gedreht
|
||||
// und dann neu geladen, daher keine lokale Rotation
|
||||
this.$emit('rotate', {
|
||||
direction: 'right',
|
||||
memberId: this.memberId
|
||||
});
|
||||
},
|
||||
|
||||
resetZoom() {
|
||||
this.scale = 1;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue(newVal) {
|
||||
if (!newVal) {
|
||||
// Dialog geschlossen - Reset
|
||||
this.scale = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-viewer-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 200px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.viewer-image {
|
||||
max-width: 100%;
|
||||
max-height: 60vh;
|
||||
object-fit: contain;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.no-image {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.image-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
color: var(--text-color);
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: var(--primary-light);
|
||||
border-color: var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.extra-content {
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
padding: 0.5rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.viewer-image {
|
||||
max-height: 50vh;
|
||||
}
|
||||
|
||||
.image-actions {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
229
frontend/src/components/InfoDialog.vue
Normal file
229
frontend/src/components/InfoDialog.vue
Normal file
@@ -0,0 +1,229 @@
|
||||
<template>
|
||||
<BaseDialog
|
||||
:model-value="modelValue"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
:title="title"
|
||||
:is-modal="true"
|
||||
:size="size"
|
||||
:closable="true"
|
||||
:close-on-overlay="closeOnOverlay"
|
||||
@close="handleClose"
|
||||
>
|
||||
<!-- Content -->
|
||||
<div class="info-content">
|
||||
<div v-if="icon" class="info-icon" :class="`icon-${type}`">
|
||||
{{ computedIcon }}
|
||||
</div>
|
||||
<div class="info-message">
|
||||
{{ message }}
|
||||
</div>
|
||||
<div v-if="details" class="info-details">
|
||||
{{ details }}
|
||||
</div>
|
||||
<div v-if="$slots.default" class="info-extra">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer mit OK-Button -->
|
||||
<template #footer>
|
||||
<button
|
||||
@click="handleOk"
|
||||
:class="buttonClass"
|
||||
>
|
||||
{{ okText }}
|
||||
</button>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseDialog from './BaseDialog.vue';
|
||||
|
||||
export default {
|
||||
name: 'InfoDialog',
|
||||
components: {
|
||||
BaseDialog
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Information'
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
details: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'info',
|
||||
validator: (value) => ['info', 'success', 'warning', 'error'].includes(value)
|
||||
},
|
||||
icon: {
|
||||
type: [String, Boolean],
|
||||
default: true
|
||||
},
|
||||
okText: {
|
||||
type: String,
|
||||
default: 'OK'
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'small'
|
||||
},
|
||||
closeOnOverlay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
computedIcon() {
|
||||
// Wenn ein eigenes Icon übergeben wurde
|
||||
if (typeof this.icon === 'string') {
|
||||
return this.icon;
|
||||
}
|
||||
|
||||
// Wenn icon=false, kein Icon
|
||||
if (this.icon === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Standard-Icons je nach Typ
|
||||
const icons = {
|
||||
info: 'ℹ️',
|
||||
success: '✅',
|
||||
warning: '⚠️',
|
||||
error: '⛔'
|
||||
};
|
||||
return icons[this.type] || icons.info;
|
||||
},
|
||||
buttonClass() {
|
||||
const classes = {
|
||||
info: 'btn-primary',
|
||||
success: 'btn-success',
|
||||
warning: 'btn-warning',
|
||||
error: 'btn-danger'
|
||||
};
|
||||
return classes[this.type] || 'btn-primary';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleOk() {
|
||||
this.$emit('ok');
|
||||
this.handleClose();
|
||||
},
|
||||
handleClose() {
|
||||
this.$emit('update:modelValue', false);
|
||||
this.$emit('close');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.info-content {
|
||||
text-align: center;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.icon-info {
|
||||
color: #17a2b8;
|
||||
}
|
||||
|
||||
.icon-success {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.icon-warning {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.icon-error {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.info-message {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-details {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-muted);
|
||||
margin-top: 0.5rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.info-extra {
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.btn-primary,
|
||||
.btn-success,
|
||||
.btn-warning,
|
||||
.btn-danger {
|
||||
padding: 0.5rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: var(--primary-hover);
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-warning:hover {
|
||||
background: #e0a800;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -40,6 +40,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -47,6 +68,8 @@ import { ref, computed, onMounted, watch } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
export default {
|
||||
name: 'SeasonSelector',
|
||||
props: {
|
||||
@@ -129,7 +152,7 @@ export default {
|
||||
if (error.response?.data?.error === 'alreadyexists') {
|
||||
alert('Diese Saison existiert bereits!');
|
||||
} else {
|
||||
alert('Fehler beim Erstellen der Saison');
|
||||
this.showInfo('Fehler', 'Fehler beim Erstellen der Saison', '', 'error');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
169
frontend/src/composables/useDialog.js
Normal file
169
frontend/src/composables/useDialog.js
Normal file
@@ -0,0 +1,169 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
/**
|
||||
* Composable für einfache Dialog-Verwaltung
|
||||
*
|
||||
* Verwendung:
|
||||
*
|
||||
* // In der Component:
|
||||
* const { isOpen, open, close } = useDialog();
|
||||
*
|
||||
* // Dialog öffnen:
|
||||
* open();
|
||||
*
|
||||
* // Dialog schließen:
|
||||
* close();
|
||||
*
|
||||
* // Im Template:
|
||||
* <BaseDialog v-model="isOpen" ...>
|
||||
*/
|
||||
export function useDialog(initialState = false) {
|
||||
const isOpen = ref(initialState);
|
||||
|
||||
const open = () => {
|
||||
isOpen.value = true;
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
isOpen.value = false;
|
||||
};
|
||||
|
||||
const toggle = () => {
|
||||
isOpen.value = !isOpen.value;
|
||||
};
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
open,
|
||||
close,
|
||||
toggle
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable für Confirm-Dialog
|
||||
*
|
||||
* Verwendung:
|
||||
*
|
||||
* const { confirm } = useConfirm();
|
||||
*
|
||||
* const deleted = await confirm({
|
||||
* title: 'Löschen bestätigen',
|
||||
* message: 'Möchten Sie diesen Eintrag wirklich löschen?',
|
||||
* type: 'danger'
|
||||
* });
|
||||
*
|
||||
* if (deleted) {
|
||||
* // Löschvorgang durchführen
|
||||
* }
|
||||
*/
|
||||
export function useConfirm() {
|
||||
const isOpen = ref(false);
|
||||
const config = ref({});
|
||||
let resolvePromise = null;
|
||||
|
||||
const confirm = (options = {}) => {
|
||||
config.value = {
|
||||
title: options.title || 'Bestätigung',
|
||||
message: options.message || 'Möchten Sie fortfahren?',
|
||||
details: options.details || '',
|
||||
type: options.type || 'info',
|
||||
confirmText: options.confirmText || 'OK',
|
||||
cancelText: options.cancelText || 'Abbrechen',
|
||||
showCancel: options.showCancel !== false
|
||||
};
|
||||
|
||||
isOpen.value = true;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
isOpen.value = false;
|
||||
if (resolvePromise) {
|
||||
resolvePromise(true);
|
||||
resolvePromise = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
isOpen.value = false;
|
||||
if (resolvePromise) {
|
||||
resolvePromise(false);
|
||||
resolvePromise = null;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
config,
|
||||
confirm,
|
||||
handleConfirm,
|
||||
handleCancel
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable für Info-Dialog
|
||||
*
|
||||
* Verwendung:
|
||||
*
|
||||
* const { showInfo } = useInfo();
|
||||
*
|
||||
* await showInfo({
|
||||
* title: 'Erfolg',
|
||||
* message: 'Der Vorgang wurde erfolgreich abgeschlossen.',
|
||||
* type: 'success'
|
||||
* });
|
||||
*/
|
||||
export function useInfo() {
|
||||
const isOpen = ref(false);
|
||||
const config = ref({});
|
||||
let resolvePromise = null;
|
||||
|
||||
const showInfo = (options = {}) => {
|
||||
config.value = {
|
||||
title: options.title || 'Information',
|
||||
message: options.message || '',
|
||||
details: options.details || '',
|
||||
type: options.type || 'info',
|
||||
okText: options.okText || 'OK',
|
||||
icon: options.icon !== undefined ? options.icon : true,
|
||||
size: options.size || 'small',
|
||||
closeOnOverlay: options.closeOnOverlay !== false
|
||||
};
|
||||
|
||||
isOpen.value = true;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
isOpen.value = false;
|
||||
if (resolvePromise) {
|
||||
resolvePromise(true);
|
||||
resolvePromise = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
isOpen.value = false;
|
||||
if (resolvePromise) {
|
||||
resolvePromise(false);
|
||||
resolvePromise = null;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
config,
|
||||
showInfo,
|
||||
handleOk,
|
||||
handleClose
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,13 +3,68 @@
|
||||
<h2>Activate Account</h2>
|
||||
<button @click="activate">Activate</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
export default {
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleConfirmResult(confirmed) {
|
||||
if (this.confirmDialog.resolveCallback) {
|
||||
this.confirmDialog.resolveCallback(confirmed);
|
||||
this.confirmDialog.resolveCallback = null;
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
|
||||
async activate() {
|
||||
try {
|
||||
const activationCode = this.$route.params.activationCode;
|
||||
|
||||
@@ -26,12 +26,35 @@
|
||||
<button @click="requestAccess">Zugriff beantragen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import apiClient from '../apiClient';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
export default {
|
||||
name: "ClubView",
|
||||
computed: {
|
||||
@@ -39,6 +62,22 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Dialog States
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
},
|
||||
confirmDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
resolveCallback: null
|
||||
},
|
||||
club: {
|
||||
name: '',
|
||||
},
|
||||
@@ -47,6 +86,38 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleConfirmResult(confirmed) {
|
||||
if (this.confirmDialog.resolveCallback) {
|
||||
this.confirmDialog.resolveCallback(confirmed);
|
||||
this.confirmDialog.resolveCallback = null;
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
|
||||
async loadClub() {
|
||||
try {
|
||||
const response = await apiClient.get(`/clubs/${this.currentClub}`);
|
||||
@@ -61,7 +132,7 @@ export default {
|
||||
const response = await apiClient.get(`/clubmembers/notapproved/${this.currentClub}`);
|
||||
this.openRequests = response.data;
|
||||
} catch (error) {
|
||||
alert('Fehler beim Laden der offenen Anfragen');
|
||||
this.showInfo('Fehler', 'Fehler beim Laden der offenen Anfragen', '', 'error');
|
||||
}
|
||||
},
|
||||
async requestAccess() {
|
||||
|
||||
@@ -4,20 +4,91 @@
|
||||
<label>Name des Vereins: <input type="text" v-model="clubName" /></label>
|
||||
<button @click="createClub">Verein anlegen</button>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex/dist/vuex.cjs.js';
|
||||
import apiClient from '../apiClient';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
export default {
|
||||
name: "CreateClubView",
|
||||
data() {
|
||||
return {
|
||||
// Dialog States
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
},
|
||||
confirmDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
resolveCallback: null
|
||||
},
|
||||
clubName: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleConfirmResult(confirmed) {
|
||||
if (this.confirmDialog.resolveCallback) {
|
||||
this.confirmDialog.resolveCallback(confirmed);
|
||||
this.confirmDialog.resolveCallback = null;
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
|
||||
...mapActions(['setClubs', 'setCurrentClub']),
|
||||
async createClub() {
|
||||
if (this.clubName.trim().length < 3) {
|
||||
|
||||
@@ -338,14 +338,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showImage || showRenderModal" class="memberImage" @click="closeImage">
|
||||
<template v-if="showImage">
|
||||
<img :src="imageUrl" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- Image Dialog für normale Bilder -->
|
||||
<ImageDialog
|
||||
v-model="showImage"
|
||||
title="Aktivitätsbild"
|
||||
:image-url="imageUrl"
|
||||
/>
|
||||
|
||||
<!-- Dialog für gerenderte Zeichnungen -->
|
||||
<BaseDialog
|
||||
v-model="showRenderModal"
|
||||
title="Aktivitätszeichnung"
|
||||
size="large"
|
||||
>
|
||||
<div class="render-container">
|
||||
<CourtDrawingRender :drawing-data="renderModalData" :width="560" :height="360" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
<div v-if="showAccidentForm" class="accidentForm">
|
||||
<form @submit.prevent="submitAccident">
|
||||
<div>
|
||||
@@ -415,6 +424,26 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -424,12 +453,39 @@ import Multiselect from 'vue-multiselect';
|
||||
import Sortable from 'sortablejs';
|
||||
import PDFGenerator from '../components/PDFGenerator.js';
|
||||
import CourtDrawingRender from '../components/CourtDrawingRender.vue';
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
import ImageDialog from '../components/ImageDialog.vue';
|
||||
import BaseDialog from '../components/BaseDialog.vue';
|
||||
|
||||
export default {
|
||||
name: 'DiaryView',
|
||||
components: { Multiselect, CourtDrawingRender },
|
||||
components: {
|
||||
Multiselect,
|
||||
CourtDrawingRender,
|
||||
InfoDialog,
|
||||
ConfirmDialog,
|
||||
ImageDialog,
|
||||
BaseDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Dialog States
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
},
|
||||
confirmDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
resolveCallback: null
|
||||
},
|
||||
date: null,
|
||||
dates: [],
|
||||
showForm: false,
|
||||
@@ -568,6 +624,38 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleConfirmResult(confirmed) {
|
||||
if (this.confirmDialog.resolveCallback) {
|
||||
this.confirmDialog.resolveCallback(confirmed);
|
||||
this.confirmDialog.resolveCallback = null;
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
|
||||
hasActivityVisual(pa) {
|
||||
if (!pa) return false;
|
||||
try {
|
||||
@@ -744,7 +832,7 @@ export default {
|
||||
// Direkt auf das leere Tagebuch des neuen Datums wechseln
|
||||
await this.handleDateChange();
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -756,9 +844,9 @@ export default {
|
||||
trainingStart: this.trainingStart || null,
|
||||
trainingEnd: this.trainingEnd || null,
|
||||
});
|
||||
alert('Trainingszeiten erfolgreich aktualisiert.');
|
||||
this.showInfo('Erfolg', 'Trainingszeiten erfolgreich aktualisiert.', '', 'success');
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -789,7 +877,7 @@ export default {
|
||||
const response = await apiClient.get('/predefined-activities');
|
||||
this.predefinedActivities = response.data;
|
||||
} catch (error) {
|
||||
alert('Fehler beim Laden der vordefinierten Aktivitäten');
|
||||
this.showInfo('Fehler', 'Fehler beim Laden der vordefinierten Aktivitäten', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -863,7 +951,7 @@ export default {
|
||||
label: tag.tag.label
|
||||
}));
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
this.doMemberTagUpdates = true;
|
||||
},
|
||||
@@ -879,7 +967,7 @@ export default {
|
||||
this.newNoteContent = '';
|
||||
this.selectedTagsNotes = [];
|
||||
} else {
|
||||
alert('Bitte wählen Sie einen Teilnehmer aus und geben Sie einen Notiztext ein.');
|
||||
this.showInfo('Hinweis', 'Bitte wählen Sie einen Teilnehmer aus und geben Sie einen Notiztext ein.', '', 'warning');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -908,7 +996,7 @@ export default {
|
||||
this.availableTags.push(newTag);
|
||||
this.selectedActivityTags.push(newTag);
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -927,7 +1015,7 @@ export default {
|
||||
this.selectedMemberTags.push(newTag);
|
||||
await this.linkTagToMemberAndDate(newTag);
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -942,7 +1030,7 @@ export default {
|
||||
tagId: tagId
|
||||
});
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -955,7 +1043,7 @@ export default {
|
||||
tagId: tagId
|
||||
});
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -972,7 +1060,7 @@ export default {
|
||||
}
|
||||
this.previousActivityTags = [...selectedTags];
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -988,7 +1076,7 @@ export default {
|
||||
}
|
||||
this.previousMemberTags = [...this.selectedMemberTags];
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1001,7 +1089,7 @@ export default {
|
||||
});
|
||||
this.selectedMemberTags = this.selectedMemberTags.filter(tag => tag.id !== tagId);
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1014,7 +1102,7 @@ export default {
|
||||
});
|
||||
this.notes = this.notes.filter(note => note.content !== noteContent);
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1026,7 +1114,7 @@ export default {
|
||||
});
|
||||
this.selectedActivityTags = this.selectedActivityTags.filter(t => t.id !== tagId);
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1052,7 +1140,7 @@ export default {
|
||||
const list = await apiClient.get(`/diary/${this.currentClub}`).then(r => r.data);
|
||||
if (!list.some(e => String(e.id) === String(this.date?.id))) {
|
||||
await this.refreshDates();
|
||||
alert('Ausgewähltes Datum war nicht mehr aktuell. Bitte erneut versuchen.');
|
||||
this.showInfo('Hinweis', 'Ausgewähltes Datum war nicht mehr aktuell. Bitte erneut versuchen.', '', 'warning');
|
||||
return;
|
||||
}
|
||||
await apiClient.post(`/diary-date-activities/${this.currentClub}`, {
|
||||
@@ -1081,19 +1169,20 @@ export default {
|
||||
const msg = (error && error.response && error.response.data && error.response.data.error) || 'Ein Fehler ist aufgetreten.';
|
||||
if (msg.toLowerCase().includes('foreign key') || msg.toLowerCase().includes('constraint')) {
|
||||
await this.refreshDates();
|
||||
alert('Datum war nicht (mehr) vorhanden. Die Datums-Auswahl wurde aktualisiert. Bitte erneut versuchen.');
|
||||
this.showInfo('Hinweis', 'Datum war nicht (mehr) vorhanden. Die Datums-Auswahl wurde aktualisiert. Bitte erneut versuchen.', '', 'warning');
|
||||
} else {
|
||||
alert(msg);
|
||||
this.showInfo('Fehler', msg, '', 'error');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async deleteCurrentDate() {
|
||||
if (!this.canDeleteCurrentDate) {
|
||||
alert('Datum kann nicht gelöscht werden: Es sind noch Inhalte vorhanden (Trainingplan, Teilnehmer, Aktivitäten, Unfälle oder Notizen).');
|
||||
this.showInfo('Hinweis', 'Datum kann nicht gelöscht werden', 'Es sind noch Inhalte vorhanden (Trainingplan, Teilnehmer, Aktivitäten, Unfälle oder Notizen).', 'warning');
|
||||
return;
|
||||
}
|
||||
if (!confirm('Dieses Datum wirklich löschen?')) return;
|
||||
const confirmed = await this.showConfirm('Löschen bestätigen', 'Möchten Sie dieses Datum wirklich löschen?', 'Alle zugehörigen Daten werden ebenfalls gelöscht.', 'danger');
|
||||
if (!confirmed) return;
|
||||
try {
|
||||
await apiClient.delete(`/diary/${this.currentClub}/${this.date.id}`);
|
||||
await this.refreshDates();
|
||||
@@ -1104,7 +1193,7 @@ export default {
|
||||
this.trainingPlan = [];
|
||||
} catch (e) {
|
||||
const msg = (e && e.response && e.response.data && e.response.data.error) || 'Fehler beim Löschen.';
|
||||
alert(msg);
|
||||
this.showInfo('Fehler', msg, '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1115,7 +1204,7 @@ export default {
|
||||
});
|
||||
this.calculateIntermediateTimes();
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1125,7 +1214,7 @@ export default {
|
||||
this.trainingPlan = this.trainingPlan.filter(item => item.id !== planItemId);
|
||||
this.calculateIntermediateTimes();
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1207,7 +1296,7 @@ export default {
|
||||
await apiClient.delete(`/diary-date-activities/${this.currentClub}/${planItemId}`);
|
||||
this.trainingPlan = this.trainingPlan.filter(item => item.id !== planItemId);
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
async onDragEnd(evt) {
|
||||
@@ -1218,7 +1307,7 @@ export default {
|
||||
});
|
||||
this.recalculateTimes();
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
/* async loadMemberImage(member) {
|
||||
@@ -1254,6 +1343,10 @@ export default {
|
||||
this.showImage = false;
|
||||
this.showRenderModal = false;
|
||||
this.renderModalData = null;
|
||||
// Revoke blob URL wenn vorhanden
|
||||
if (this.imageUrl && this.imageUrl.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(this.imageUrl);
|
||||
}
|
||||
this.imageUrl = '';
|
||||
},
|
||||
|
||||
@@ -1275,7 +1368,7 @@ export default {
|
||||
this.showImage = true;
|
||||
} catch (e) {
|
||||
console.error('Bild laden fehlgeschlagen:', e);
|
||||
alert('Bild konnte nicht geladen werden.');
|
||||
this.showInfo('Fehler', 'Bild konnte nicht geladen werden.', '', 'error');
|
||||
}
|
||||
},
|
||||
async openActivityVisual(pa) {
|
||||
@@ -1364,7 +1457,7 @@ export default {
|
||||
});
|
||||
this.editingGroupId = null;
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
cancelEditGroup() {
|
||||
@@ -1547,7 +1640,7 @@ export default {
|
||||
this.editingActivityId = null;
|
||||
this.editingActivityText = '';
|
||||
} catch (error) {
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1678,7 +1771,7 @@ export default {
|
||||
if (this.activityMembersMap[activityId]) this.activityMembersMap[activityId].delete(participantId);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Fehler beim Aktualisieren der Aktivitäts-Teilnehmer');
|
||||
this.showInfo('Fehler', 'Fehler beim Aktualisieren der Aktivitäts-Teilnehmer', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1759,14 +1852,14 @@ export default {
|
||||
this.closeQuickAddDialog();
|
||||
|
||||
// Zeige Erfolgsmeldung
|
||||
alert(`Mitglied "${newMember.firstName} ${newMember.lastName}" wurde erfolgreich erstellt und hinzugefügt!`);
|
||||
this.showInfo('Erfolg', `Mitglied "${newMember.firstName} ${newMember.lastName}" wurde erfolgreich erstellt und hinzugefügt!`, '', 'success');
|
||||
} else {
|
||||
throw new Error('Fehler beim Erstellen des Mitglieds');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen des Mitglieds:', error);
|
||||
alert('Fehler beim Erstellen des Mitglieds: ' + (error.response?.data?.error || error.message));
|
||||
this.showInfo('Fehler', 'Fehler beim Erstellen des Mitglieds: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -10,20 +10,91 @@
|
||||
<p>Noch kein Konto? <router-link to="/register">Registrieren</router-link></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { mapActions } from 'vuex';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// Dialog States
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
},
|
||||
confirmDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
resolveCallback: null
|
||||
},
|
||||
email: '',
|
||||
password: '',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleConfirmResult(confirmed) {
|
||||
if (this.confirmDialog.resolveCallback) {
|
||||
this.confirmDialog.resolveCallback(confirmed);
|
||||
this.confirmDialog.resolveCallback = null;
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
|
||||
...mapActions(['login']),
|
||||
async executeLogin() {
|
||||
try {
|
||||
|
||||
@@ -101,28 +101,42 @@
|
||||
<button @click.stop="openNotesModal(member)">Notizen</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-if="showImageModal" class="modal">
|
||||
<div class="modal-content image-modal-content">
|
||||
<span class="close" @click="closeImageModal">×</span>
|
||||
<div class="image-container">
|
||||
<img :src="selectedImageUrl" alt="Großes Mitgliedsbild"
|
||||
class="modal-image">
|
||||
<div class="image-actions">
|
||||
<button @click="rotateImage('left')" class="rotate-btn" title="90° links drehen">
|
||||
↺ Links
|
||||
</button>
|
||||
<button @click="rotateImage('right')" class="rotate-btn" title="90° rechts drehen">
|
||||
↻ Rechts
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Image Viewer Dialog -->
|
||||
<ImageViewerDialog
|
||||
v-model="showImageModal"
|
||||
title="Mitgliedsbild"
|
||||
:image-url="selectedImageUrl"
|
||||
:member-id="selectedMemberId"
|
||||
:show-actions="true"
|
||||
:allow-rotate="true"
|
||||
@rotate="handleRotate"
|
||||
/>
|
||||
|
||||
<div v-if="showNotesModal" class="modal">
|
||||
<div class="modal-content">
|
||||
@@ -141,6 +155,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -148,13 +183,37 @@ import { mapGetters } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
import PDFGenerator from '../components/PDFGenerator.js';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
import ImageViewerDialog from '../components/ImageViewerDialog.vue';
|
||||
export default {
|
||||
name: 'MembersView',
|
||||
components: {
|
||||
InfoDialog,
|
||||
ConfirmDialog,
|
||||
ImageViewerDialog
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isAuthenticated', 'currentClub']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Dialog States
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
},
|
||||
confirmDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
resolveCallback: null
|
||||
},
|
||||
members: [],
|
||||
memberFormIsOpen: false,
|
||||
newFirstname: '',
|
||||
@@ -185,6 +244,38 @@ export default {
|
||||
await this.init();
|
||||
},
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleConfirmResult(confirmed) {
|
||||
if (this.confirmDialog.resolveCallback) {
|
||||
this.confirmDialog.resolveCallback(confirmed);
|
||||
this.confirmDialog.resolveCallback = null;
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
|
||||
async init() {
|
||||
await this.loadMembers();
|
||||
},
|
||||
@@ -257,7 +348,7 @@ export default {
|
||||
response = await apiClient.post(`/clubmembers/set/${this.currentClub}`, memberData);
|
||||
this.loadMembers();
|
||||
} catch (error) {
|
||||
alert('Fehler beim Speichern des Mitglieds');
|
||||
this.showInfo('Fehler', 'Fehler beim Speichern des Mitglieds', '', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -364,26 +455,31 @@ export default {
|
||||
this.selectedImageUrl = null;
|
||||
this.selectedMemberId = null;
|
||||
},
|
||||
async rotateImage(direction) {
|
||||
if (!this.selectedMemberId) return;
|
||||
async handleRotate(event) {
|
||||
const { direction, memberId } = event;
|
||||
if (!memberId) return;
|
||||
|
||||
try {
|
||||
const response = await apiClient.post(`/clubmembers/rotate-image/${this.currentClub}/${this.selectedMemberId}`, {
|
||||
const response = await apiClient.post(`/clubmembers/rotate-image/${this.currentClub}/${memberId}`, {
|
||||
direction: direction
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
// Reload the member's image to show the rotated version
|
||||
const member = this.members.find(m => m.id === this.selectedMemberId);
|
||||
const member = this.members.find(m => m.id === memberId);
|
||||
if (member) {
|
||||
await this.reloadMemberImage(member);
|
||||
// Update the modal image URL
|
||||
if (member.imageUrl) {
|
||||
this.selectedImageUrl = member.imageUrl;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert('Fehler beim Drehen des Bildes: ' + response.data.error);
|
||||
this.showInfo('Fehler', 'Fehler beim Drehen des Bildes: ' + response.data.error, '', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Drehen des Bildes:', error);
|
||||
alert('Fehler beim Drehen des Bildes: ' + (error.response?.data?.error || error.message));
|
||||
this.showInfo('Fehler', 'Fehler beim Drehen des Bildes: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
}
|
||||
},
|
||||
async loadMemberImage(member) {
|
||||
@@ -462,7 +558,7 @@ export default {
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren der Ratings:', error);
|
||||
const message = error.response?.data?.error || error.response?.data?.message || 'Fehler beim Aktualisieren der TTR/QTTR-Werte';
|
||||
alert(message);
|
||||
this.showInfo('Fehler', message, '', 'error');
|
||||
} finally {
|
||||
this.isUpdatingRatings = false;
|
||||
}
|
||||
|
||||
@@ -67,19 +67,60 @@
|
||||
@saved="onAccountSaved"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '../apiClient.js';
|
||||
import MyTischtennisDialog from '../components/MyTischtennisDialog.vue';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
export default {
|
||||
name: 'MyTischtennisAccount',
|
||||
components: {
|
||||
MyTischtennisDialog
|
||||
},
|
||||
,
|
||||
InfoDialog,
|
||||
ConfirmDialog},
|
||||
data() {
|
||||
return {
|
||||
// Dialog States
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
},
|
||||
confirmDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
resolveCallback: null
|
||||
},
|
||||
loading: true,
|
||||
account: null,
|
||||
showDialog: false
|
||||
@@ -89,6 +130,38 @@ export default {
|
||||
this.loadAccount();
|
||||
},
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleConfirmResult(confirmed) {
|
||||
if (this.confirmDialog.resolveCallback) {
|
||||
this.confirmDialog.resolveCallback(confirmed);
|
||||
this.confirmDialog.resolveCallback = null;
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
|
||||
async loadAccount() {
|
||||
try {
|
||||
this.loading = true;
|
||||
|
||||
@@ -173,7 +173,28 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -249,8 +270,50 @@
|
||||
<span v-else>{{ item.placement || '–' }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
<tr v-if="!participantsGroups.length">
|
||||
<td colspan="6"><em>Keine Einträge für den gewählten Filter.</em></td>
|
||||
</tr>
|
||||
@@ -357,6 +420,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -364,10 +448,28 @@ import apiClient from '../apiClient.js';
|
||||
import { mapGetters } from 'vuex';
|
||||
import PDFGenerator from '../components/PDFGenerator.js';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
export default {
|
||||
name: 'OfficialTournaments',
|
||||
data() {
|
||||
return {
|
||||
// Dialog States
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
},
|
||||
confirmDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
resolveCallback: null
|
||||
},
|
||||
selectedFile: null,
|
||||
uploadedId: null,
|
||||
parsed: null,
|
||||
@@ -510,6 +612,38 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleConfirmResult(confirmed) {
|
||||
if (this.confirmDialog.resolveCallback) {
|
||||
this.confirmDialog.resolveCallback(confirmed);
|
||||
this.confirmDialog.resolveCallback = null;
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
|
||||
switchTopTab(tab) { this.topActiveTab = tab; if (tab === 'participations') this.loadClubParticipations(); },
|
||||
compareMembers(a, b) {
|
||||
const fnA = String(a.firstName || '');
|
||||
@@ -849,7 +983,7 @@ export default {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren des Status:', error);
|
||||
alert('Fehler beim Aktualisieren des Status: ' + (error.response?.data?.error || error.message));
|
||||
this.showInfo('Fehler', 'Fehler beim Aktualisieren des Status: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
}
|
||||
},
|
||||
async updatePlacement(item, value) {
|
||||
@@ -860,7 +994,7 @@ export default {
|
||||
await this.saveParticipation(competitionId, memberId);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren der Platzierung:', error);
|
||||
alert('Fehler beim Aktualisieren der Platzierung: ' + (error.response?.data?.error || error.message));
|
||||
this.showInfo('Fehler', 'Fehler beim Aktualisieren der Platzierung: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
}
|
||||
},
|
||||
async updateStatusForCompetition(competition, member, action) {
|
||||
@@ -885,7 +1019,7 @@ export default {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren des Status:', error);
|
||||
alert('Fehler beim Aktualisieren des Status: ' + (error.response?.data?.error || error.message));
|
||||
this.showInfo('Fehler', 'Fehler beim Aktualisieren des Status: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
}
|
||||
},
|
||||
// Auswahl Helfer + PDF-Generierung
|
||||
|
||||
@@ -28,16 +28,55 @@
|
||||
<p>Keine ausstehenden Benutzeranfragen.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
export default {
|
||||
name: 'PendingApprovalsView',
|
||||
data() {
|
||||
return {
|
||||
// Dialog States
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
},
|
||||
confirmDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
resolveCallback: null
|
||||
},
|
||||
pendingUsers: [],
|
||||
};
|
||||
},
|
||||
@@ -48,12 +87,44 @@ export default {
|
||||
await this.loadPendingApprovals();
|
||||
},
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleConfirmResult(confirmed) {
|
||||
if (this.confirmDialog.resolveCallback) {
|
||||
this.confirmDialog.resolveCallback(confirmed);
|
||||
this.confirmDialog.resolveCallback = null;
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
|
||||
async loadPendingApprovals() {
|
||||
try {
|
||||
const response = await apiClient.get(`/clubs/pending/${this.currentClub}`);
|
||||
this.pendingUsers = response.data.map(entry => entry.user);
|
||||
} catch (error) {
|
||||
alert('Fehler beim Laden der ausstehenden Anfragen');
|
||||
this.showInfo('Fehler', 'Fehler beim Laden der ausstehenden Anfragen', '', 'error');
|
||||
}
|
||||
},
|
||||
async approveUser(userId) {
|
||||
@@ -64,7 +135,7 @@ export default {
|
||||
});
|
||||
this.pendingUsers = this.pendingUsers.filter(user => user.id !== userId);
|
||||
} catch (error) {
|
||||
alert('Fehler beim Genehmigen des Benutzers');
|
||||
this.showInfo('Fehler', 'Fehler beim Genehmigen des Benutzers', '', 'error');
|
||||
}
|
||||
},
|
||||
async rejectUser(userId) {
|
||||
@@ -75,7 +146,7 @@ export default {
|
||||
});
|
||||
this.pendingUsers = this.pendingUsers.filter(user => user.id !== userId);
|
||||
} catch (error) {
|
||||
alert('Fehler beim Ablehnen des Benutzers');
|
||||
this.showInfo('Fehler', 'Fehler beim Ablehnen des Benutzers', '', 'error');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -105,19 +105,60 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '../apiClient.js';
|
||||
import CourtDrawingTool from '../components/CourtDrawingTool.vue';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
export default {
|
||||
name: 'PredefinedActivities',
|
||||
components: {
|
||||
CourtDrawingTool
|
||||
},
|
||||
,
|
||||
InfoDialog,
|
||||
ConfirmDialog},
|
||||
data() {
|
||||
return {
|
||||
// Dialog States
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
},
|
||||
confirmDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
resolveCallback: null
|
||||
},
|
||||
activities: [],
|
||||
selectedActivity: null,
|
||||
editModel: null,
|
||||
@@ -148,6 +189,38 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleConfirmResult(confirmed) {
|
||||
if (this.confirmDialog.resolveCallback) {
|
||||
this.confirmDialog.resolveCallback(confirmed);
|
||||
this.confirmDialog.resolveCallback = null;
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
|
||||
parseDrawingData(value) {
|
||||
if (!value) return null;
|
||||
if (typeof value === 'object') return value;
|
||||
@@ -281,18 +354,20 @@ export default {
|
||||
},
|
||||
async deleteImage(imageId) {
|
||||
if (!this.editModel || !this.editModel.id) return;
|
||||
if (!confirm('Bild wirklich löschen?')) return;
|
||||
const confirmed = await this.showConfirm('Bestätigung', 'Bild wirklich löschen?', '', 'warning');
|
||||
if (!confirmed) return;
|
||||
try {
|
||||
await apiClient.delete(`/predefined-activities/${this.editModel.id}/image/${imageId}`);
|
||||
// Nach Löschen Details neu laden
|
||||
await this.select(this.editModel);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen des Bildes:', error);
|
||||
alert('Fehler beim Löschen des Bildes');
|
||||
this.showInfo('Fehler', 'Fehler beim Löschen des Bildes', '', 'error');
|
||||
}
|
||||
},
|
||||
async deduplicate() {
|
||||
if (!confirm('Alle Aktivitäten mit identischem Namen werden zusammengeführt. Fortfahren?')) return;
|
||||
const confirmed = await this.showConfirm('Bestätigung', 'Alle Aktivitäten mit identischem Namen werden zusammengeführt. Fortfahren?', '', 'warning');
|
||||
if (!confirmed) return;
|
||||
await apiClient.post('/predefined-activities/deduplicate', {});
|
||||
await this.reload();
|
||||
},
|
||||
|
||||
@@ -11,19 +11,90 @@
|
||||
<p>Bereits ein Konto? <router-link to="/login">Zum Login</router-link></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// Dialog States
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
},
|
||||
confirmDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
resolveCallback: null
|
||||
},
|
||||
email: '',
|
||||
password: '',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleConfirmResult(confirmed) {
|
||||
if (this.confirmDialog.resolveCallback) {
|
||||
this.confirmDialog.resolveCallback(confirmed);
|
||||
this.confirmDialog.resolveCallback = null;
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
|
||||
async register() {
|
||||
try {
|
||||
await axios.post(`${import.meta.env.VITE_BACKEND}/api/auth/register`, { email: this.email, password: this.password });
|
||||
|
||||
@@ -86,6 +86,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -95,17 +116,37 @@ import PDFGenerator from '../components/PDFGenerator.js';
|
||||
import SeasonSelector from '../components/SeasonSelector.vue';
|
||||
import MatchReportDialog from '../components/MatchReportDialog.vue';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
export default {
|
||||
name: 'ScheduleView',
|
||||
components: {
|
||||
SeasonSelector,
|
||||
MatchReportDialog
|
||||
},
|
||||
,
|
||||
InfoDialog,
|
||||
ConfirmDialog},
|
||||
computed: {
|
||||
...mapGetters(['isAuthenticated', 'currentClub', 'clubs', 'currentClubName']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Dialog States
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
},
|
||||
confirmDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
resolveCallback: null
|
||||
},
|
||||
showImportModal: false,
|
||||
selectedFile: null,
|
||||
leagues: [],
|
||||
@@ -117,6 +158,38 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleConfirmResult(confirmed) {
|
||||
if (this.confirmDialog.resolveCallback) {
|
||||
this.confirmDialog.resolveCallback(confirmed);
|
||||
this.confirmDialog.resolveCallback = null;
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
|
||||
...mapActions(['openDialog']),
|
||||
openImportModal() {
|
||||
this.showImportModal = true;
|
||||
@@ -141,11 +214,11 @@ export default {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
alert('Spielplan erfolgreich importiert!');
|
||||
this.showInfo('Erfolg', 'Spielplan erfolgreich importiert!', '', 'success');
|
||||
this.closeImportModal();
|
||||
this.loadLeagues();
|
||||
} catch (error) {
|
||||
alert('Fehler beim Importieren der CSV-Datei');
|
||||
this.showInfo('Fehler', 'Fehler beim Importieren der CSV-Datei', '', 'error');
|
||||
}
|
||||
},
|
||||
// Sortierfunktion für Ligen
|
||||
@@ -215,7 +288,7 @@ export default {
|
||||
this.leagues = this.sortLeagues(response.data);
|
||||
} catch (error) {
|
||||
console.error('ScheduleView: Error loading leagues:', error);
|
||||
alert('Fehler beim Laden der Ligen');
|
||||
this.showInfo('Fehler', 'Fehler beim Laden der Ligen', '', 'error');
|
||||
}
|
||||
},
|
||||
onSeasonChange(season) {
|
||||
@@ -231,7 +304,7 @@ export default {
|
||||
const response = await apiClient.get(`/matches/leagues/${this.currentClub}/matches/${leagueId}`);
|
||||
this.matches = response.data;
|
||||
} catch (error) {
|
||||
alert('Fehler beim Laden der Matches');
|
||||
this.showInfo('Fehler', 'Fehler beim Laden der Matches', '', 'error');
|
||||
this.matches = [];
|
||||
}
|
||||
},
|
||||
@@ -242,7 +315,7 @@ export default {
|
||||
const response = await apiClient.get(`/matches/leagues/${this.currentClub}/matches${seasonParam}`);
|
||||
this.matches = response.data;
|
||||
} catch (error) {
|
||||
alert('Fehler beim Laden des Gesamtspielplans');
|
||||
this.showInfo('Fehler', 'Fehler beim Laden des Gesamtspielplans', '', 'error');
|
||||
this.matches = [];
|
||||
}
|
||||
},
|
||||
@@ -260,7 +333,7 @@ export default {
|
||||
return !isYouth;
|
||||
});
|
||||
} catch (error) {
|
||||
alert('Fehler beim Laden des Erwachsenenspielplans');
|
||||
this.showInfo('Fehler', 'Fehler beim Laden des Erwachsenenspielplans', '', 'error');
|
||||
this.matches = [];
|
||||
}
|
||||
},
|
||||
|
||||
@@ -199,6 +199,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -207,11 +228,15 @@ import { useStore } from 'vuex';
|
||||
import SeasonSelector from '../components/SeasonSelector.vue';
|
||||
import apiClient from '../apiClient.js';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
export default {
|
||||
name: 'TeamManagementView',
|
||||
components: {
|
||||
SeasonSelector
|
||||
},
|
||||
,
|
||||
InfoDialog,
|
||||
ConfirmDialog},
|
||||
setup() {
|
||||
const store = useStore();
|
||||
|
||||
@@ -488,10 +513,10 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
alert(message);
|
||||
this.showInfo('Fehler', message, '', 'error');
|
||||
} else {
|
||||
// Für andere Dateitypen nur Upload-Bestätigung
|
||||
alert(`${pendingUploadType.value === 'code_list' ? 'Code-Liste' : 'Pin-Liste'} "${pendingUploadFile.value.name}" wurde erfolgreich hochgeladen!`);
|
||||
this.showInfo('Information', `${pendingUploadType.value === 'code_list' ? 'Code-Liste' : 'Pin-Liste'} "${pendingUploadFile.value.name}" wurde erfolgreich hochgeladen!`, '', 'info');
|
||||
}
|
||||
|
||||
// Dokumente neu laden
|
||||
@@ -499,7 +524,7 @@ export default {
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Hochladen und Parsen der Datei:', error);
|
||||
alert('Fehler beim Hochladen und Parsen der Datei');
|
||||
this.showInfo('Fehler', 'Fehler beim Hochladen und Parsen der Datei', '', 'error');
|
||||
} finally {
|
||||
parsingInProgress.value = false;
|
||||
pendingUploadFile.value = null;
|
||||
@@ -550,10 +575,10 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
alert(message);
|
||||
this.showInfo('Fehler', message, '', 'error');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Parsen der PDF:', error);
|
||||
alert('Fehler beim Parsen der PDF-Datei');
|
||||
this.showInfo('Fehler', 'Fehler beim Parsen der PDF-Datei', '', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -597,7 +622,7 @@ export default {
|
||||
showPDFViewer.value = true;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden des PDFs:', error);
|
||||
alert('Fehler beim Laden des PDFs');
|
||||
this.showInfo('Fehler', 'Fehler beim Laden des PDFs', '', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -43,7 +43,28 @@
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
<button @click="removeParticipant(participant)" style="margin-left:0.5rem" class="trash-btn">
|
||||
🗑️
|
||||
</button>
|
||||
@@ -161,10 +182,52 @@
|
||||
<span v-else>
|
||||
{{ getPlayerName(m.player1) }} – <strong>{{ getPlayerName(m.player2) }}</strong>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ getPlayerName(m.player1) }} – {{ getPlayerName(m.player2) }}
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@@ -181,14 +244,77 @@
|
||||
class="inline-input"
|
||||
ref="editInput"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span @click="startEditResult(m, r)" class="result-text clickable">
|
||||
{{ r.pointsPlayer1 }}:{{ r.pointsPlayer2 }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
<span v-if="!isLastResult(m, r)">, </span>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Eingabefeld für neue Sätze (immer sichtbar solange offen) -->
|
||||
<div class="new-set-line">
|
||||
@@ -196,12 +322,54 @@
|
||||
@keyup.enter="saveMatchResult(m, m.resultInput)"
|
||||
@blur="saveMatchResult(m, m.resultInput)" class="inline-input" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 2. Fall: Match ist abgeschlossen → Read‑only -->
|
||||
<template v-else>
|
||||
{{ formatResult(m) }}
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
</td>
|
||||
<td>
|
||||
{{ getSetsString(m) }}
|
||||
@@ -257,10 +425,52 @@
|
||||
<span v-else>
|
||||
{{ getPlayerName(m.player1) }} – <strong>{{ getPlayerName(m.player2) }}</strong>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ getPlayerName(m.player1) }} – {{ getPlayerName(m.player2) }}
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
</td>
|
||||
<td>
|
||||
<!-- 1. Fall: Match ist noch offen → Edit‑Mode -->
|
||||
@@ -276,14 +486,77 @@
|
||||
class="inline-input"
|
||||
ref="editInput"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span @click="startEditResult(m, r)" class="result-text clickable">
|
||||
{{ r.pointsPlayer1 }}:{{ r.pointsPlayer2 }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
<span v-if="!isLastResult(m, r)">, </span>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Eingabefeld für neue Sätze (immer sichtbar solange offen) -->
|
||||
<div class="new-set-line">
|
||||
@@ -291,12 +564,54 @@
|
||||
@keyup.enter="saveMatchResult(m, m.resultInput)"
|
||||
@blur="saveMatchResult(m, m.resultInput)" class="inline-input" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 2. Fall: Match ist abgeschlossen → Read‑only -->
|
||||
<template v-else>
|
||||
{{ formatResult(m) }}
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
</td>
|
||||
<td>
|
||||
{{ getSetsString(m) }}
|
||||
@@ -330,16 +645,55 @@
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '../apiClient';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
export default {
|
||||
name: 'TournamentsView',
|
||||
data() {
|
||||
return {
|
||||
// Dialog States
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
},
|
||||
confirmDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
resolveCallback: null
|
||||
},
|
||||
selectedDate: 'new',
|
||||
newDate: '',
|
||||
dates: [],
|
||||
@@ -521,6 +875,38 @@ export default {
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleConfirmResult(confirmed) {
|
||||
if (this.confirmDialog.resolveCallback) {
|
||||
this.confirmDialog.resolveCallback(confirmed);
|
||||
this.confirmDialog.resolveCallback = null;
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
|
||||
normalizeResultInput(raw) {
|
||||
const s = raw.trim();
|
||||
if (s.includes(':')) {
|
||||
@@ -660,7 +1046,7 @@ export default {
|
||||
this.newDate = '';
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen des Turniers:', error);
|
||||
alert('Fehler beim Erstellen des Turniers: ' + (error.response?.data?.error || error.message));
|
||||
this.showInfo('Fehler', 'Fehler beim Erstellen des Turniers: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -715,8 +1101,7 @@ export default {
|
||||
});
|
||||
this.participants = r.data;
|
||||
} catch (err) {
|
||||
alert('Fehler beim Zufällig‑Verteilen:\n' +
|
||||
(err.response?.data?.error || err.message));
|
||||
this.showInfo('Fehler', 'Fehler beim Zufällig‑Verteilen:\n' + (err.response?.data?.error || err.message), '', 'error');
|
||||
}
|
||||
await this.loadTournamentData();
|
||||
},
|
||||
@@ -740,7 +1125,7 @@ export default {
|
||||
);
|
||||
const updated = allRes.data.find(m2 => m2.id === match.id);
|
||||
if (!updated) {
|
||||
alert('Fehler beim Aktualisieren des Matches');
|
||||
this.showInfo('Fehler', 'Fehler beim Aktualisieren des Matches', '', 'error');
|
||||
return;
|
||||
}
|
||||
match.tournamentResults = updated.tournamentResults || [];
|
||||
@@ -950,7 +1335,7 @@ export default {
|
||||
});
|
||||
await this.loadTournamentData();
|
||||
} catch (err) {
|
||||
alert('Fehler beim Zurücksetzen der K.o.-Runde');
|
||||
this.showInfo('Fehler', 'Fehler beim Zurücksetzen der K.o.-Runde', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1175,7 +1560,7 @@ export default {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Trainingsteilnehmer:', error);
|
||||
alert('Fehler beim Laden der Trainingsteilnehmer: ' + (error.response?.data?.error || error.message));
|
||||
this.showInfo('Fehler', 'Fehler beim Laden der Trainingsteilnehmer: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user