Files
stechuhr3/frontend/src/views/Permissions.vue

389 lines
8.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="permissions-page">
<div class="card">
<!-- Informationstext -->
<div class="info-box">
<h3>Arbeitszeiten teilen</h3>
<p>
Hier können Sie Email-Adressen eintragen, die berechtigt sind, Ihre aktuellen Arbeitszeiten
und Ihr voraussichtliches Arbeitsende zu sehen. Dies ist nützlich, wenn Familie oder Freunde
wissen möchten, wann Sie mit der Arbeit fertig sind.
</p>
</div>
<!-- Formular zum Hinzufügen eines Beobachters -->
<form @submit.prevent="addWatcher" class="watcher-form">
<div class="form-row">
<div class="form-group">
<label for="email">Email-Adresse hinzufügen</label>
<input
type="email"
id="email"
v-model="form.email"
placeholder="name@beispiel.de"
required
>
</div>
<div class="form-group button-group">
<button type="submit" class="btn btn-primary" :disabled="loading">
{{ loading ? 'Wird hinzugefügt...' : 'Hinzufügen' }}
</button>
</div>
</div>
</form>
<hr>
<!-- Tabelle mit berechtigten Personen -->
<h3>Berechtigte Personen</h3>
<table class="watchers-table">
<thead>
<tr>
<th>Email-Adresse</th>
<th>Hinzugefügt am</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-if="watchers.length === 0">
<td colspan="3" class="no-data">
Keine berechtigten Personen eingetragen
</td>
</tr>
<tr v-for="watcher in watchers" :key="watcher.id">
<td>{{ watcher.email }}</td>
<td>{{ formatDateTime(watcher.createdAt) }}</td>
<td>
<button
@click="removeWatcher(watcher.id, watcher.email)"
class="btn btn-delete-small"
:disabled="loading"
>
×
</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Modal-Komponente -->
<Modal
v-if="showModal"
:show="showModal"
:title="modalConfig.title"
:message="modalConfig.message"
:type="modalConfig.type"
:confirmText="modalConfig.confirmText"
:cancelText="modalConfig.cancelText"
@confirm="onConfirm"
@cancel="onCancel"
/>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useAuthStore } from '../stores/authStore'
import { useModal } from '../composables/useModal'
import Modal from '../components/Modal.vue'
const authStore = useAuthStore()
const watchers = ref([])
const loading = ref(false)
const { showModal, modalConfig, alert, confirm, onConfirm, onCancel } = useModal()
const form = ref({
email: ''
})
// Lade alle Watcher
async function loadWatchers() {
try {
const response = await fetch('http://localhost:3010/api/watcher', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
})
if (!response.ok) {
throw new Error('Fehler beim Laden der Berechtigungen')
}
watchers.value = await response.json()
} catch (error) {
console.error('Fehler beim Laden der Berechtigungen:', error)
await alert(`Fehler: ${error.message}`, 'Fehler')
}
}
// Füge Watcher hinzu
async function addWatcher() {
if (!form.value.email) {
await alert('Bitte geben Sie eine Email-Adresse ein', 'Fehlende Angaben')
return
}
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/watcher', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authStore.token}`
},
body: JSON.stringify({
email: form.value.email
})
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || 'Fehler beim Hinzufügen der Berechtigung')
}
await alert('Berechtigung erfolgreich hinzugefügt', 'Erfolg')
// Formular zurücksetzen
form.value.email = ''
// Liste neu laden
await loadWatchers()
} catch (error) {
console.error('Fehler beim Hinzufügen der Berechtigung:', error)
await alert(`Fehler: ${error.message}`, 'Fehler')
} finally {
loading.value = false
}
}
// Entferne Watcher
async function removeWatcher(id, email) {
const confirmed = await confirm(
`Möchten Sie die Berechtigung für ${email} wirklich entfernen?`,
'Berechtigung entfernen'
)
if (!confirmed) {
return
}
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/watcher/${id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authStore.token}`
}
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || 'Fehler beim Entfernen der Berechtigung')
}
await loadWatchers()
} catch (error) {
console.error('Fehler beim Entfernen der Berechtigung:', error)
await alert(`Fehler: ${error.message}`, 'Fehler')
} finally {
loading.value = false
}
}
// Formatiere Datum und Zeit
function formatDateTime(dateStr) {
if (!dateStr) return ''
// Prüfe ob dateStr bereits ein Date-Objekt ist
const date = dateStr instanceof Date ? dateStr : new Date(dateStr)
// Prüfe auf ungültiges Datum
if (isNaN(date.getTime())) {
console.error('Ungültiges Datum:', dateStr)
return '—'
}
return date.toLocaleString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
}
// Initiales Laden
onMounted(() => {
loadWatchers()
})
</script>
<style scoped>
.permissions-page {
max-width: 900px;
margin: 0 auto;
padding: 20px;
}
.card {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.info-box {
background: #e8f5e9;
border: 1px solid #c8e6c9;
border-radius: 4px;
padding: 16px;
margin-bottom: 24px;
}
.info-box h3 {
margin: 0 0 8px 0;
font-size: 16px;
color: #2e7d32;
}
.info-box p {
margin: 0;
font-size: 13px;
color: #1b5e20;
line-height: 1.6;
}
.watcher-form {
margin-bottom: 16px;
}
.form-row {
display: flex;
gap: 16px;
align-items: flex-end;
}
.form-group {
flex: 1;
display: flex;
flex-direction: column;
gap: 6px;
}
.form-group.button-group {
flex: 0 0 auto;
}
.form-group label {
font-weight: 600;
font-size: 14px;
color: #333;
}
.form-group input {
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
font-family: inherit;
}
.form-group input:focus {
outline: none;
border-color: #4CAF50;
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
}
hr {
border: none;
border-top: 1px solid #eee;
margin: 20px 0;
}
h3 {
margin: 0 0 12px 0;
font-size: 16px;
color: #333;
}
.watchers-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.watchers-table th {
text-align: left;
padding: 10px 8px;
background: #f5f5f5;
border-bottom: 2px solid #ddd;
font-weight: 600;
font-size: 12px;
color: #666;
}
.watchers-table th:last-child {
width: 40px;
}
.watchers-table td {
padding: 10px 8px;
border-bottom: 1px solid #eee;
}
.watchers-table tbody tr:hover {
background: #f9f9f9;
}
.no-data {
text-align: center;
color: #999;
font-style: italic;
padding: 20px !important;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
font-family: inherit;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background: linear-gradient(135deg, #4CAF50, #45a049);
color: white;
box-shadow: 0 2px 4px rgba(76, 175, 80, 0.3);
}
.btn-primary:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(76, 175, 80, 0.4);
}
.btn-delete-small {
background: #f44336;
color: white;
padding: 4px 8px;
font-size: 16px;
line-height: 1;
border-radius: 3px;
}
.btn-delete-small:hover:not(:disabled) {
background: #da190b;
}
</style>