Dummydaten und Sprache angepasst
Dieser Commit ist enthalten in:
@ -1,11 +1,11 @@
|
|||||||
# SkillMate - Mitarbeiter-Skills-Management für Sicherheitsbehörden
|
# SkillMate - Skill-Management für Sicherheitsbehörden
|
||||||
|
|
||||||
SkillMate ist eine spezialisierte Anwendung zur Verwaltung von Mitarbeiterfähigkeiten und -kompetenzen in Sicherheitsbehörden. Die Anwendung läuft lokal bei jedem Nutzer und kann über ein Admin-Panel zentral verwaltet werden.
|
SkillMate ist eine spezialisierte Anwendung zur Verwaltung von Fähigkeiten und Kompetenzen der Mitarbeitenden in Sicherheitsbehörden. Die Anwendung läuft lokal bei jedem Nutzer und kann über ein Admin-Panel zentral verwaltet werden.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- 🔍 **Skill-basierte Suche**: Finden Sie Mitarbeiter anhand ihrer Fähigkeiten
|
- 🔍 **Skill-basierte Suche**: Finden Sie Mitarbeitende anhand ihrer Fähigkeiten
|
||||||
- 👥 **Mitarbeiterverwaltung**: Umfassende Profile mit Skills, Sprachen und Spezialisierungen
|
- 👥 **Mitarbeitendenverwaltung**: Umfassende Profile mit Skills, Sprachen und Spezialisierungen
|
||||||
- 🔐 **Sicherheitsüberprüfungen**: Verwaltung von Ü2/Ü3-Clearances
|
- 🔐 **Sicherheitsüberprüfungen**: Verwaltung von Ü2/Ü3-Clearances
|
||||||
- 🌓 **Dark/Light Mode**: Anpassbare Benutzeroberfläche
|
- 🌓 **Dark/Light Mode**: Anpassbare Benutzeroberfläche
|
||||||
- 🔄 **Synchronisation**: Zentrale Datenverwaltung über Admin-Panel
|
- 🔄 **Synchronisation**: Zentrale Datenverwaltung über Admin-Panel
|
||||||
|
|||||||
@ -46,9 +46,9 @@ export default function CreateEmployee() {
|
|||||||
case 'stellvertreter':
|
case 'stellvertreter':
|
||||||
return 'Stellvertretung'
|
return 'Stellvertretung'
|
||||||
case 'beauftragter':
|
case 'beauftragter':
|
||||||
return 'Beauftragte:r'
|
return 'Beauftragte Person'
|
||||||
default:
|
default:
|
||||||
return 'Mitarbeiter:in'
|
return 'Teammitglied'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,9 +73,9 @@ export default function CreateEmployee() {
|
|||||||
|
|
||||||
if (response.data.data?.temporaryPassword) {
|
if (response.data.data?.temporaryPassword) {
|
||||||
setCreatedUser({ password: response.data.data.temporaryPassword })
|
setCreatedUser({ password: response.data.data.temporaryPassword })
|
||||||
setSuccess(`Mitarbeiter und Benutzerkonto erfolgreich erstellt!`)
|
setSuccess(`Profil und Benutzerkonto erfolgreich erstellt!`)
|
||||||
} else {
|
} else {
|
||||||
setSuccess('Mitarbeiter erfolgreich erstellt!')
|
setSuccess('Profil erfolgreich erstellt!')
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Error:', err.response?.data)
|
console.error('Error:', err.response?.data)
|
||||||
@ -96,8 +96,8 @@ export default function CreateEmployee() {
|
|||||||
const getRoleDescription = (role: UserRole): string => {
|
const getRoleDescription = (role: UserRole): string => {
|
||||||
const descriptions = {
|
const descriptions = {
|
||||||
admin: 'Vollzugriff auf alle Funktionen inklusive Admin Panel und Benutzerverwaltung',
|
admin: 'Vollzugriff auf alle Funktionen inklusive Admin Panel und Benutzerverwaltung',
|
||||||
superuser: 'Kann Mitarbeiter anlegen und verwalten, aber kein Zugriff auf Admin Panel',
|
superuser: 'Kann Mitarbeitende anlegen und verwalten, aber kein Zugriff auf Admin Panel',
|
||||||
user: 'Kann nur das eigene Profil bearbeiten und Mitarbeiter durchsuchen'
|
user: 'Kann nur das eigene Profil bearbeiten und Mitarbeitende durchsuchen'
|
||||||
}
|
}
|
||||||
return descriptions[role]
|
return descriptions[role]
|
||||||
}
|
}
|
||||||
@ -112,10 +112,10 @@ export default function CreateEmployee() {
|
|||||||
← Zurück zur Benutzerverwaltung
|
← Zurück zur Benutzerverwaltung
|
||||||
</button>
|
</button>
|
||||||
<h1 className="text-title-lg font-poppins font-bold text-primary">
|
<h1 className="text-title-lg font-poppins font-bold text-primary">
|
||||||
Neuen Mitarbeiter & Benutzer anlegen
|
Neues Mitarbeitendenprofil & Benutzer anlegen
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-body text-secondary mt-2">
|
<p className="text-body text-secondary mt-2">
|
||||||
Erstellen Sie einen neuen Mitarbeiter-Datensatz und optional ein Benutzerkonto
|
Erstellen Sie ein neues Profil und optional ein Benutzerkonto
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -141,7 +141,7 @@ export default function CreateEmployee() {
|
|||||||
{createdUser.password}
|
{createdUser.password}
|
||||||
</code>
|
</code>
|
||||||
<p className="text-sm mt-2 text-green-700">
|
<p className="text-sm mt-2 text-green-700">
|
||||||
⚠️ Bitte notieren Sie dieses Passwort und geben Sie es sicher an den Mitarbeiter weiter.
|
⚠️ Bitte notieren Sie dieses Passwort und geben Sie es sicher an die betreffende Person weiter.
|
||||||
Das Passwort muss beim ersten Login geändert werden.
|
Das Passwort muss beim ersten Login geändert werden.
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-4 flex justify-end">
|
<div className="mt-4 flex justify-end">
|
||||||
@ -160,7 +160,7 @@ export default function CreateEmployee() {
|
|||||||
|
|
||||||
<div className="card mb-6">
|
<div className="card mb-6">
|
||||||
<h2 className="text-title-card font-poppins font-semibold text-primary mb-6">
|
<h2 className="text-title-card font-poppins font-semibold text-primary mb-6">
|
||||||
Mitarbeiter-Grunddaten
|
Grunddaten des Profils
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
@ -251,7 +251,7 @@ export default function CreateEmployee() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-secondary-light mt-2">
|
<p className="text-sm text-secondary-light mt-2">
|
||||||
Wählen Sie die primäre Organisationseinheit für den neuen Mitarbeiter. Die Abteilung wird automatisch anhand der Auswahl gesetzt.
|
Wählen Sie die primäre Organisationseinheit für die neue Person. Die Abteilung wird automatisch anhand der Auswahl gesetzt.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -265,10 +265,10 @@ export default function CreateEmployee() {
|
|||||||
onChange={event => setSelectedUnitRole(event.target.value as EmployeeUnitRole)}
|
onChange={event => setSelectedUnitRole(event.target.value as EmployeeUnitRole)}
|
||||||
className="input-field w-full"
|
className="input-field w-full"
|
||||||
>
|
>
|
||||||
<option value="leiter">Leiter:in</option>
|
<option value="leiter">Leitung</option>
|
||||||
<option value="stellvertreter">Stellvertretung</option>
|
<option value="stellvertreter">Stellvertretung</option>
|
||||||
<option value="mitarbeiter">Mitarbeiter:in</option>
|
<option value="mitarbeiter">Teammitglied</option>
|
||||||
<option value="beauftragter">Beauftragte:r</option>
|
<option value="beauftragter">Beauftragte Person</option>
|
||||||
</select>
|
</select>
|
||||||
<p className="text-sm text-secondary-light mt-2">
|
<p className="text-sm text-secondary-light mt-2">
|
||||||
Diese Rolle wird für Vertretungs- und Organigramm-Funktionen verwendet.
|
Diese Rolle wird für Vertretungs- und Organigramm-Funktionen verwendet.
|
||||||
@ -296,7 +296,7 @@ export default function CreateEmployee() {
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<p className="text-sm text-secondary-light mt-2 ml-7">
|
<p className="text-sm text-secondary-light mt-2 ml-7">
|
||||||
Erstellt ein Benutzerkonto, mit dem sich der Mitarbeiter im System anmelden kann.
|
Erstellt ein Benutzerkonto, mit dem sich die angelegte Person im System anmelden kann.
|
||||||
Ein sicheres temporäres Passwort wird automatisch generiert.
|
Ein sicheres temporäres Passwort wird automatisch generiert.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -327,10 +327,10 @@ export default function CreateEmployee() {
|
|||||||
📋 Was passiert als Nächstes?
|
📋 Was passiert als Nächstes?
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-2 text-body text-secondary">
|
<ul className="space-y-2 text-body text-secondary">
|
||||||
<li>• Der Mitarbeiter wird mit Grunddaten angelegt (Position: "Mitarbeiter", Telefon: "Nicht angegeben")</li>
|
<li>• Die Person wird mit Grunddaten angelegt (Position: "Teammitglied", Telefon: "Nicht angegeben")</li>
|
||||||
<li>• {watchCreateUser ? 'Ein Benutzerkonto wird erstellt und ein temporäres Passwort generiert' : 'Kein Benutzerkonto wird erstellt'}</li>
|
<li>• {watchCreateUser ? 'Ein Benutzerkonto wird erstellt und ein temporäres Passwort generiert' : 'Kein Benutzerkonto wird erstellt'}</li>
|
||||||
<li>• {selectedUnitId ? `Die Organisationseinheit ${selectedUnitName} (${getUnitRoleLabel(selectedUnitRole)}) wird als primäre Zuordnung hinterlegt` : 'Organisationseinheit kann später im Organigramm zugewiesen werden'}</li>
|
<li>• {selectedUnitId ? `Die Organisationseinheit ${selectedUnitName} (${getUnitRoleLabel(selectedUnitRole)}) wird als primäre Zuordnung hinterlegt` : 'Organisationseinheit kann später im Organigramm zugewiesen werden'}</li>
|
||||||
<li>• Der Mitarbeiter kann später im Frontend seine Profildaten vervollständigen</li>
|
<li>• Die Person kann später im Frontend das Profil vervollständigen</li>
|
||||||
<li>• Alle Daten werden verschlüsselt in der Datenbank gespeichert</li>
|
<li>• Alle Daten werden verschlüsselt in der Datenbank gespeichert</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -349,7 +349,7 @@ export default function CreateEmployee() {
|
|||||||
className="btn-primary"
|
className="btn-primary"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading ? 'Erstelle...' : 'Mitarbeiter erstellen'}
|
{loading ? 'Erstelle...' : 'Profil erstellen'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -1,19 +1,15 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { api } from '../services/api'
|
import { api } from '../services/api'
|
||||||
import SyncStatus from '../components/SyncStatus'
|
|
||||||
|
|
||||||
interface DashboardStats {
|
interface DashboardStats {
|
||||||
totalEmployees: number
|
totalEmployees: number
|
||||||
totalSkills: number
|
totalSkills: number
|
||||||
totalUsers: number
|
totalUsers: number
|
||||||
lastSync?: {
|
|
||||||
timestamp: string
|
|
||||||
success: boolean
|
|
||||||
itemsSynced: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
|
const navigate = useNavigate()
|
||||||
const [stats, setStats] = useState<DashboardStats>({
|
const [stats, setStats] = useState<DashboardStats>({
|
||||||
totalEmployees: 0,
|
totalEmployees: 0,
|
||||||
totalSkills: 0,
|
totalSkills: 0,
|
||||||
@ -65,23 +61,19 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
const statsCards = [
|
const statsCards = [
|
||||||
{
|
{
|
||||||
title: 'Mitarbeiter',
|
title: 'Mitarbeitende',
|
||||||
value: stats.totalEmployees,
|
value: stats.totalEmployees,
|
||||||
color: 'text-primary-blue',
|
color: 'text-primary-blue',
|
||||||
bgColor: 'bg-bg-accent',
|
bgColor: 'bg-bg-accent',
|
||||||
|
path: '/users'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Skills',
|
title: 'Skills',
|
||||||
value: stats.totalSkills,
|
value: stats.totalSkills,
|
||||||
color: 'text-success',
|
color: 'text-success',
|
||||||
bgColor: 'bg-success-bg',
|
bgColor: 'bg-success-bg',
|
||||||
},
|
path: '/skills'
|
||||||
{
|
}
|
||||||
title: 'Benutzer',
|
|
||||||
value: stats.totalUsers,
|
|
||||||
color: 'text-info',
|
|
||||||
bgColor: 'bg-info-bg',
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -90,9 +82,14 @@ export default function Dashboard() {
|
|||||||
Dashboard
|
Dashboard
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||||||
{statsCards.map((stat, index) => (
|
{statsCards.map((stat, index) => (
|
||||||
<div key={index} className="card">
|
<button
|
||||||
|
key={index}
|
||||||
|
type="button"
|
||||||
|
onClick={() => navigate(stat.path)}
|
||||||
|
className="card text-left transition hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-primary-blue"
|
||||||
|
>
|
||||||
<div className={`w-16 h-16 rounded-card ${stat.bgColor} flex items-center justify-center mb-4`}>
|
<div className={`w-16 h-16 rounded-card ${stat.bgColor} flex items-center justify-center mb-4`}>
|
||||||
<span className={`text-2xl font-bold ${stat.color}`}>
|
<span className={`text-2xl font-bold ${stat.color}`}>
|
||||||
{stat.value}
|
{stat.value}
|
||||||
@ -101,60 +98,11 @@ export default function Dashboard() {
|
|||||||
<h3 className="text-body font-medium text-tertiary">
|
<h3 className="text-body font-medium text-tertiary">
|
||||||
{stat.title}
|
{stat.title}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
||||||
<div className="card">
|
|
||||||
<h2 className="text-title-card font-poppins font-semibold text-primary mb-4">
|
|
||||||
Letzte Synchronisation
|
|
||||||
</h2>
|
|
||||||
{stats.lastSync ? (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<p className="text-body text-secondary">
|
|
||||||
<span className="font-medium">Zeitpunkt:</span>{' '}
|
|
||||||
{new Date(stats.lastSync.timestamp).toLocaleString('de-DE')}
|
|
||||||
</p>
|
|
||||||
<p className="text-body text-secondary">
|
|
||||||
<span className="font-medium">Status:</span>{' '}
|
|
||||||
<span className={stats.lastSync.success ? 'text-success' : 'text-error'}>
|
|
||||||
{stats.lastSync.success ? 'Erfolgreich' : 'Fehlgeschlagen'}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p className="text-body text-secondary">
|
|
||||||
<span className="font-medium">Synchronisierte Elemente:</span>{' '}
|
|
||||||
{stats.lastSync.itemsSynced}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p className="text-body text-tertiary">
|
|
||||||
Noch keine Synchronisation durchgeführt
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="card">
|
|
||||||
<h2 className="text-title-card font-poppins font-semibold text-primary mb-4">
|
|
||||||
Systemstatus
|
|
||||||
</h2>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<p className="text-body text-secondary">
|
|
||||||
<span className="font-medium">Backend:</span>{' '}
|
|
||||||
<span className="text-success">Online</span>
|
|
||||||
</p>
|
|
||||||
<p className="text-body text-secondary">
|
|
||||||
<span className="font-medium">Datenbank:</span>{' '}
|
|
||||||
<span className="text-success">Verbunden</span>
|
|
||||||
</p>
|
|
||||||
<p className="text-body text-secondary">
|
|
||||||
<span className="font-medium">Version:</span> 1.0.0
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SyncStatus />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export default function EmployeeForm() {
|
|||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch employee:', error)
|
console.error('Failed to fetch employee:', error)
|
||||||
setError('Mitarbeiter konnte nicht geladen werden')
|
setError('Profil konnte nicht geladen werden')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,8 +80,8 @@ export default function EmployeeForm() {
|
|||||||
const getRoleDescription = (role: UserRole): string => {
|
const getRoleDescription = (role: UserRole): string => {
|
||||||
const descriptions = {
|
const descriptions = {
|
||||||
admin: 'Vollzugriff auf alle Funktionen inklusive Admin Panel und Benutzerverwaltung',
|
admin: 'Vollzugriff auf alle Funktionen inklusive Admin Panel und Benutzerverwaltung',
|
||||||
superuser: 'Kann Mitarbeiter anlegen und verwalten, aber kein Zugriff auf Admin Panel',
|
superuser: 'Kann Mitarbeitende anlegen und verwalten, aber kein Zugriff auf Admin Panel',
|
||||||
user: 'Kann nur das eigene Profil bearbeiten und Mitarbeiter durchsuchen'
|
user: 'Kann nur das eigene Profil bearbeiten und Mitarbeitende durchsuchen'
|
||||||
}
|
}
|
||||||
return descriptions[role]
|
return descriptions[role]
|
||||||
}
|
}
|
||||||
@ -96,7 +96,7 @@ export default function EmployeeForm() {
|
|||||||
← Zurück zur Übersicht
|
← Zurück zur Übersicht
|
||||||
</button>
|
</button>
|
||||||
<h1 className="text-title-lg font-poppins font-bold text-primary">
|
<h1 className="text-title-lg font-poppins font-bold text-primary">
|
||||||
{isEdit ? 'Mitarbeiter bearbeiten' : 'Neuer Mitarbeiter'}
|
{isEdit ? 'Mitarbeitendenprofil bearbeiten' : 'Neues Mitarbeitendenprofil'}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ export default function EmployeeForm() {
|
|||||||
|
|
||||||
<div className="card mb-6">
|
<div className="card mb-6">
|
||||||
<h2 className="text-title-card font-poppins font-semibold text-primary mb-6">
|
<h2 className="text-title-card font-poppins font-semibold text-primary mb-6">
|
||||||
Mitarbeiterdaten
|
Profilinformationen
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
@ -192,7 +192,7 @@ export default function EmployeeForm() {
|
|||||||
className="w-5 h-5 rounded border-border-input text-primary-blue focus:ring-primary-blue"
|
className="w-5 h-5 rounded border-border-input text-primary-blue focus:ring-primary-blue"
|
||||||
/>
|
/>
|
||||||
<span className="text-body text-secondary">
|
<span className="text-body text-secondary">
|
||||||
Benutzerkonto für diesen Mitarbeiter erstellen
|
Benutzerkonto für diese Person erstellen
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<p className="text-small text-tertiary mt-1">
|
<p className="text-small text-tertiary mt-1">
|
||||||
@ -242,7 +242,7 @@ export default function EmployeeForm() {
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="btn-primary disabled:opacity-50 disabled:cursor-not-allowed"
|
className="btn-primary disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{loading ? 'Speichern...' : (isEdit ? 'Änderungen speichern' : 'Mitarbeiter anlegen')}
|
{loading ? 'Speichern...' : (isEdit ? 'Änderungen speichern' : 'Profil anlegen')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -66,7 +66,7 @@ export default function EmployeeFormComplete() {
|
|||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch employee:', error)
|
console.error('Failed to fetch employee:', error)
|
||||||
setError('Mitarbeiter konnte nicht geladen werden')
|
setError('Profil konnte nicht geladen werden')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,15 +97,15 @@ export default function EmployeeFormComplete() {
|
|||||||
let response
|
let response
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
response = await api.put(`/employees/${id}`, payload)
|
response = await api.put(`/employees/${id}`, payload)
|
||||||
setSuccess('Mitarbeiter erfolgreich aktualisiert!')
|
setSuccess('Profil erfolgreich aktualisiert!')
|
||||||
} else {
|
} else {
|
||||||
response = await api.post('/employees', payload)
|
response = await api.post('/employees', payload)
|
||||||
|
|
||||||
if (response.data.data.temporaryPassword) {
|
if (response.data.data.temporaryPassword) {
|
||||||
setCreatedUser({ password: response.data.data.temporaryPassword })
|
setCreatedUser({ password: response.data.data.temporaryPassword })
|
||||||
setSuccess(`Mitarbeiter erfolgreich erstellt! Temporäres Passwort: ${response.data.data.temporaryPassword}`)
|
setSuccess(`Profil erfolgreich erstellt! Temporäres Passwort: ${response.data.data.temporaryPassword}`)
|
||||||
} else {
|
} else {
|
||||||
setSuccess('Mitarbeiter erfolgreich erstellt!')
|
setSuccess('Profil erfolgreich erstellt!')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,8 +131,8 @@ export default function EmployeeFormComplete() {
|
|||||||
const getRoleDescription = (role: UserRole): string => {
|
const getRoleDescription = (role: UserRole): string => {
|
||||||
const descriptions = {
|
const descriptions = {
|
||||||
admin: 'Vollzugriff auf alle Funktionen inklusive Admin Panel und Benutzerverwaltung',
|
admin: 'Vollzugriff auf alle Funktionen inklusive Admin Panel und Benutzerverwaltung',
|
||||||
superuser: 'Kann Mitarbeiter anlegen und verwalten, aber kein Zugriff auf Admin Panel',
|
superuser: 'Kann Mitarbeitende anlegen und verwalten, aber kein Zugriff auf Admin Panel',
|
||||||
user: 'Kann nur das eigene Profil bearbeiten und Mitarbeiter durchsuchen'
|
user: 'Kann nur das eigene Profil bearbeiten und Mitarbeitende durchsuchen'
|
||||||
}
|
}
|
||||||
return descriptions[role]
|
return descriptions[role]
|
||||||
}
|
}
|
||||||
@ -147,7 +147,7 @@ export default function EmployeeFormComplete() {
|
|||||||
← Zurück zur Übersicht
|
← Zurück zur Übersicht
|
||||||
</button>
|
</button>
|
||||||
<h1 className="text-title-lg font-poppins font-bold text-primary">
|
<h1 className="text-title-lg font-poppins font-bold text-primary">
|
||||||
{isEdit ? 'Mitarbeiter bearbeiten' : 'Neuer Mitarbeiter'}
|
{isEdit ? 'Mitarbeitendenprofil bearbeiten' : 'Neues Mitarbeitendenprofil'}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -207,10 +207,10 @@ export default function EmployeeFormComplete() {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-body font-medium text-secondary mb-2">
|
<label className="block text-body font-medium text-secondary mb-2">
|
||||||
Mitarbeiternummer *
|
Personalnummer *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
{...register('employeeNumber', { required: 'Mitarbeiternummer ist erforderlich' })}
|
{...register('employeeNumber', { required: 'Personalnummer ist erforderlich' })}
|
||||||
className="input-field w-full"
|
className="input-field w-full"
|
||||||
placeholder="EMP001"
|
placeholder="EMP001"
|
||||||
/>
|
/>
|
||||||
@ -346,7 +346,7 @@ export default function EmployeeFormComplete() {
|
|||||||
className="w-5 h-5 rounded border-border-input text-primary-blue focus:ring-primary-blue"
|
className="w-5 h-5 rounded border-border-input text-primary-blue focus:ring-primary-blue"
|
||||||
/>
|
/>
|
||||||
<span className="text-body font-medium text-secondary">
|
<span className="text-body font-medium text-secondary">
|
||||||
Benutzerkonto für diesen Mitarbeiter erstellen
|
Benutzerkonto für diese Person erstellen
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<p className="text-sm text-secondary-light mt-1 ml-7">
|
<p className="text-sm text-secondary-light mt-1 ml-7">
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export default function EmployeeManagement() {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<p className="text-tertiary">Mitarbeiter werden geladen...</p>
|
<p className="text-tertiary">Mitarbeitende werden geladen...</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -42,13 +42,13 @@ export default function EmployeeManagement() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center mb-8">
|
<div className="flex justify-between items-center mb-8">
|
||||||
<h1 className="text-title-lg font-poppins font-bold text-primary">
|
<h1 className="text-title-lg font-poppins font-bold text-primary">
|
||||||
Mitarbeiterverwaltung
|
Mitarbeitendenverwaltung
|
||||||
</h1>
|
</h1>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/employees/new')}
|
onClick={() => navigate('/employees/new')}
|
||||||
className="btn-primary"
|
className="btn-primary"
|
||||||
>
|
>
|
||||||
Neuer Mitarbeiter
|
Neues Mitarbeitendenprofil
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ export default function EmployeeManagement() {
|
|||||||
|
|
||||||
{filteredEmployees.length === 0 && (
|
{filteredEmployees.length === 0 && (
|
||||||
<div className="text-center py-8">
|
<div className="text-center py-8">
|
||||||
<p className="text-tertiary">Keine Mitarbeiter gefunden</p>
|
<p className="text-tertiary">Keine Mitarbeitenden gefunden</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -68,7 +68,7 @@ const OrganizationNode = ({ data }: { data: any }) => {
|
|||||||
</h4>
|
</h4>
|
||||||
{data.employeeCount !== undefined && (
|
{data.employeeCount !== undefined && (
|
||||||
<p className="text-[11px] text-slate-600 mt-1">
|
<p className="text-[11px] text-slate-600 mt-1">
|
||||||
👥 {data.employeeCount} Mitarbeiter
|
👥 {data.employeeCount} Mitarbeitende
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{data.hasFuehrungsstelle && (
|
{data.hasFuehrungsstelle && (
|
||||||
|
|||||||
@ -454,7 +454,7 @@ export default function SyncSettings() {
|
|||||||
checked={syncSettings.syncEmployees}
|
checked={syncSettings.syncEmployees}
|
||||||
onChange={(e) => setSyncSettings({ ...syncSettings, syncEmployees: e.target.checked })}
|
onChange={(e) => setSyncSettings({ ...syncSettings, syncEmployees: e.target.checked })}
|
||||||
/>
|
/>
|
||||||
<span className="text-sm">Mitarbeiterdaten</span>
|
<span className="text-sm">Daten der Mitarbeitenden</span>
|
||||||
</label>
|
</label>
|
||||||
<label className="flex items-center">
|
<label className="flex items-center">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@ -277,7 +277,7 @@ export default function UserManagement() {
|
|||||||
onClick={() => navigate('/users/create-employee')}
|
onClick={() => navigate('/users/create-employee')}
|
||||||
className="btn-primary"
|
className="btn-primary"
|
||||||
>
|
>
|
||||||
+ Neuen Mitarbeiter anlegen
|
+ Neues Mitarbeitendenprofil anlegen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -303,7 +303,7 @@ export default function UserManagement() {
|
|||||||
Benutzer
|
Benutzer
|
||||||
</th>
|
</th>
|
||||||
<th className="text-left py-3 px-4 font-poppins font-medium text-secondary">
|
<th className="text-left py-3 px-4 font-poppins font-medium text-secondary">
|
||||||
Mitarbeiter
|
Mitarbeitende
|
||||||
</th>
|
</th>
|
||||||
<th className="text-left py-3 px-4 font-poppins font-medium text-secondary">
|
<th className="text-left py-3 px-4 font-poppins font-medium text-secondary">
|
||||||
Rolle
|
Rolle
|
||||||
@ -483,9 +483,9 @@ export default function UserManagement() {
|
|||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-2 text-body text-secondary">
|
<ul className="space-y-2 text-body text-secondary">
|
||||||
<li>• <strong>Administrator:</strong> Vollzugriff auf alle Funktionen und Einstellungen</li>
|
<li>• <strong>Administrator:</strong> Vollzugriff auf alle Funktionen und Einstellungen</li>
|
||||||
<li>• <strong>Poweruser:</strong> Kann Mitarbeiter und Skills verwalten, aber keine Systemeinstellungen ändern</li>
|
<li>• <strong>Poweruser:</strong> Kann Mitarbeitende und Skills verwalten, aber keine Systemeinstellungen ändern</li>
|
||||||
<li>• <strong>Benutzer:</strong> Kann nur eigenes Profil bearbeiten und Daten einsehen</li>
|
<li>• <strong>Benutzer:</strong> Kann nur eigenes Profil bearbeiten und Daten einsehen</li>
|
||||||
<li>• Neue Benutzer können über den Import oder die Mitarbeiterverwaltung angelegt werden</li>
|
<li>• Neue Benutzer können über den Import oder die Mitarbeitendenverwaltung angelegt werden</li>
|
||||||
<li>• Der Admin-Benutzer kann nicht gelöscht werden</li>
|
<li>• Der Admin-Benutzer kann nicht gelöscht werden</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -442,4 +442,4 @@ db.transaction(() => {
|
|||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
||||||
console.log(`✅ ${SAMPLE_EMPLOYEES.length} Demo-Mitarbeiter mit Passwort "${PASSWORD}" angelegt.`)
|
console.log(`✅ ${SAMPLE_EMPLOYEES.length} Demo-Mitarbeitende mit Passwort "${PASSWORD}" angelegt.`)
|
||||||
|
|||||||
@ -165,7 +165,7 @@ function buildEmployeeWithDetails(emp: any): Employee {
|
|||||||
export function createEmployee(input: EmployeeInput, actorUserId: string): { id: string } {
|
export function createEmployee(input: EmployeeInput, actorUserId: string): { id: string } {
|
||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
const employeeId = uuidv4()
|
const employeeId = uuidv4()
|
||||||
const position = input.position || 'Mitarbeiter'
|
const position = input.position || 'Teammitglied'
|
||||||
const phone = input.phone || 'Nicht angegeben'
|
const phone = input.phone || 'Nicht angegeben'
|
||||||
const availability = input.availability || 'available'
|
const availability = input.availability || 'available'
|
||||||
const employeeNumber = input.employeeNumber || `EMP${Date.now()}`
|
const employeeNumber = input.employeeNumber || `EMP${Date.now()}`
|
||||||
@ -256,7 +256,7 @@ export function updateEmployee(id: string, input: EmployeeInput, actorUserId: st
|
|||||||
updated_at = ?, updated_by = ?
|
updated_at = ?, updated_by = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`).run(
|
`).run(
|
||||||
input.firstName, input.lastName, input.position || 'Mitarbeiter', input.department,
|
input.firstName, input.lastName, input.position || 'Teammitglied', input.department,
|
||||||
// encryption handled in secure db layer caller; here store encrypted values already in input? Route prepares with FieldEncryption
|
// encryption handled in secure db layer caller; here store encrypted values already in input? Route prepares with FieldEncryption
|
||||||
input.email, // already encrypted by route layer
|
input.email, // already encrypted by route layer
|
||||||
null, // email_hash set by route if needed
|
null, // email_hash set by route if needed
|
||||||
|
|||||||
@ -264,7 +264,7 @@ router.post('/',
|
|||||||
lastName,
|
lastName,
|
||||||
employeeNumber || null,
|
employeeNumber || null,
|
||||||
photo || null,
|
photo || null,
|
||||||
position || 'Mitarbeiter', // Default position
|
position || 'Teammitglied', // Default position
|
||||||
resolvedDepartment,
|
resolvedDepartment,
|
||||||
email,
|
email,
|
||||||
phone || 'Nicht angegeben', // Default phone
|
phone || 'Nicht angegeben', // Default phone
|
||||||
@ -339,7 +339,7 @@ router.post('/',
|
|||||||
lastName,
|
lastName,
|
||||||
employeeNumber: employeeNumber || null,
|
employeeNumber: employeeNumber || null,
|
||||||
photo: photo || null,
|
photo: photo || null,
|
||||||
position: position || 'Mitarbeiter',
|
position: position || 'Teammitglied',
|
||||||
department: resolvedDepartment,
|
department: resolvedDepartment,
|
||||||
email,
|
email,
|
||||||
phone: phone || 'Nicht angegeben',
|
phone: phone || 'Nicht angegeben',
|
||||||
|
|||||||
@ -354,7 +354,7 @@ router.post('/',
|
|||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
firstName, lastName, employeeNumber, photo, position = 'Mitarbeiter',
|
firstName, lastName, employeeNumber, photo, position = 'Teammitglied',
|
||||||
department, email, phone = 'Nicht angegeben', mobile, office, availability = 'available',
|
department, email, phone = 'Nicht angegeben', mobile, office, availability = 'available',
|
||||||
clearance, skills = [], languages = [], specializations = [], userRole, createUser,
|
clearance, skills = [], languages = [], specializations = [], userRole, createUser,
|
||||||
primaryUnitId, assignmentRole
|
primaryUnitId, assignmentRole
|
||||||
@ -612,7 +612,7 @@ router.put('/:id',
|
|||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
firstName, lastName, position = 'Mitarbeiter', department, email, phone = 'Nicht angegeben',
|
firstName, lastName, position = 'Teammitglied', department, email, phone = 'Nicht angegeben',
|
||||||
mobile, office, availability = 'available', clearance, skills, languages, specializations,
|
mobile, office, availability = 'available', clearance, skills, languages, specializations,
|
||||||
employeeNumber
|
employeeNumber
|
||||||
} = req.body
|
} = req.body
|
||||||
|
|||||||
@ -39,7 +39,7 @@ export async function createEmployeeUC(req: Request, body: any, actorUserId: str
|
|||||||
lastName: body.lastName,
|
lastName: body.lastName,
|
||||||
employeeNumber: body.employeeNumber || null,
|
employeeNumber: body.employeeNumber || null,
|
||||||
photo: body.photo || null,
|
photo: body.photo || null,
|
||||||
position: body.position || 'Mitarbeiter',
|
position: body.position || 'Teammitglied',
|
||||||
department: body.department,
|
department: body.department,
|
||||||
email: body.email,
|
email: body.email,
|
||||||
phone: body.phone || 'Nicht angegeben',
|
phone: body.phone || 'Nicht angegeben',
|
||||||
|
|||||||
@ -55,8 +55,8 @@
|
|||||||
"shortcutName": "SkillMate",
|
"shortcutName": "SkillMate",
|
||||||
"runAfterFinish": true,
|
"runAfterFinish": true,
|
||||||
"menuCategory": true,
|
"menuCategory": true,
|
||||||
"description": "Mitarbeiter-Skills-Management für Sicherheitsbehörden",
|
"description": "Skill-Management für Sicherheitsbehörden",
|
||||||
"branding": "SkillMate - Professionelle Mitarbeiterverwaltung",
|
"branding": "SkillMate - Professionelles Skill-Management",
|
||||||
"vendor": "SkillMate Development",
|
"vendor": "SkillMate Development",
|
||||||
"installerHeaderIcon": "build/icon.ico",
|
"installerHeaderIcon": "build/icon.ico",
|
||||||
"ui": {
|
"ui": {
|
||||||
@ -81,7 +81,7 @@
|
|||||||
"createStartMenuShortcut": true,
|
"createStartMenuShortcut": true,
|
||||||
"shortcutName": "SkillMate",
|
"shortcutName": "SkillMate",
|
||||||
"menuCategory": true,
|
"menuCategory": true,
|
||||||
"description": "Mitarbeiter-Skills-Management für Sicherheitsbehörden",
|
"description": "Skill-Management für Sicherheitsbehörden",
|
||||||
"language": "1031",
|
"language": "1031",
|
||||||
"multiLanguageInstaller": false,
|
"multiLanguageInstaller": false,
|
||||||
"installerHeader": "build/installer-header.bmp",
|
"installerHeader": "build/installer-header.bmp",
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>SkillMate - Mitarbeiter-Skills-Management</title>
|
<title>SkillMate - Skill-Management</title>
|
||||||
<script>
|
<script>
|
||||||
// Define process for Electron renderer
|
// Define process for Electron renderer
|
||||||
window.process = {
|
window.process = {
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>SkillMate - Mitarbeiter-Skills-Management</title>
|
<title>SkillMate - Skill-Management</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@ -31,7 +31,7 @@ UninstallDisplayName={#MyAppName}
|
|||||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||||
VersionInfoVersion={#MyAppVersion}
|
VersionInfoVersion={#MyAppVersion}
|
||||||
VersionInfoCompany={#MyAppPublisher}
|
VersionInfoCompany={#MyAppPublisher}
|
||||||
VersionInfoDescription=Mitarbeiter-Skills-Management für Sicherheitsbehörden
|
VersionInfoDescription=Skill-Management für Sicherheitsbehörden
|
||||||
VersionInfoCopyright=Copyright (C) 2024 {#MyAppPublisher}
|
VersionInfoCopyright=Copyright (C) 2024 {#MyAppPublisher}
|
||||||
VersionInfoProductName={#MyAppName}
|
VersionInfoProductName={#MyAppName}
|
||||||
VersionInfoProductVersion={#MyAppVersion}
|
VersionInfoProductVersion={#MyAppVersion}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@skillmate/frontend",
|
"name": "@skillmate/frontend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "SkillMate - Mitarbeiter-Skills-Management für Sicherheitsbehörden",
|
"description": "SkillMate - Skill-Management für Sicherheitsbehörden",
|
||||||
"author": "SkillMate Development",
|
"author": "SkillMate Development",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -80,7 +80,7 @@ export default function DeputyManagement() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDelegate = async (assignmentId: string) => {
|
const handleDelegate = async (assignmentId: string) => {
|
||||||
const toDeputyId = prompt('Bitte geben Sie die Mitarbeiter-ID des neuen Vertreters ein:')
|
const toDeputyId = prompt('Bitte geben Sie die Personen-ID des neuen Vertretenden ein:')
|
||||||
if (!toDeputyId) return
|
if (!toDeputyId) return
|
||||||
|
|
||||||
const reason = prompt('Grund für die Weitergabe (optional):')
|
const reason = prompt('Grund für die Weitergabe (optional):')
|
||||||
|
|||||||
@ -426,7 +426,7 @@ export default function OfficeMap3D({ targetEmployeeId, targetRoom, currentUserR
|
|||||||
<p className="text-sm text-gray-400">{roomDetails.floorName}</p>
|
<p className="text-sm text-gray-400">{roomDetails.floorName}</p>
|
||||||
{roomDetails.occupantNames && roomDetails.occupantNames.length > 0 && (
|
{roomDetails.occupantNames && roomDetails.occupantNames.length > 0 && (
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-sm text-gray-400">Mitarbeiter:</p>
|
<p className="text-sm text-gray-400">Mitarbeitende:</p>
|
||||||
{roomDetails.occupantNames.map((name, i) => (
|
{roomDetails.occupantNames.map((name, i) => (
|
||||||
<p key={i} className="text-sm">• {name}</p>
|
<p key={i} className="text-sm">• {name}</p>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export default function OfficeMapModal({
|
|||||||
<div className="flex justify-between items-center p-4 border-b border-gray-700">
|
<div className="flex justify-between items-center p-4 border-b border-gray-700">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-semibold text-white">
|
<h2 className="text-xl font-semibold text-white">
|
||||||
Wegbeschreibung zu {employeeName || 'Mitarbeiter'}
|
Wegbeschreibung zu {employeeName || 'dieser Person'}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm text-gray-400 mt-1">
|
<p className="text-sm text-gray-400 mt-1">
|
||||||
<span className="text-green-400">● Start:</span> Ihr Büro
|
<span className="text-green-400">● Start:</span> Ihr Büro
|
||||||
|
|||||||
@ -321,7 +321,7 @@ export default function OrganizationChart({ onClose }: OrganizationChartProps) {
|
|||||||
<div className="border-b border-gray-200 dark:border-gray-700 mb-4">
|
<div className="border-b border-gray-200 dark:border-gray-700 mb-4">
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<button className="pb-2 border-b-2 border-blue-500 dark:text-white">
|
<button className="pb-2 border-b-2 border-blue-500 dark:text-white">
|
||||||
Mitarbeiter
|
Mitarbeitende
|
||||||
</button>
|
</button>
|
||||||
<button className="pb-2 dark:text-gray-400">
|
<button className="pb-2 dark:text-gray-400">
|
||||||
Skills
|
Skills
|
||||||
@ -353,7 +353,7 @@ export default function OrganizationChart({ onClose }: OrganizationChartProps) {
|
|||||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||||
{emp.role === 'leiter' && '🔑 Leitung'}
|
{emp.role === 'leiter' && '🔑 Leitung'}
|
||||||
{emp.role === 'stellvertreter' && '↔️ Stellvertretung'}
|
{emp.role === 'stellvertreter' && '↔️ Stellvertretung'}
|
||||||
{emp.role === 'mitarbeiter' && 'Mitarbeiter'}
|
{emp.role === 'mitarbeiter' && 'Mitarbeitende Person'}
|
||||||
{emp.role === 'beauftragter' && '🎖️ Beauftragter'}
|
{emp.role === 'beauftragter' && '🎖️ Beauftragter'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -361,7 +361,7 @@ export default function OrganizationChart({ onClose }: OrganizationChartProps) {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{unitEmployees.length === 0 && (
|
{unitEmployees.length === 0 && (
|
||||||
<p className="text-gray-500 dark:text-gray-400">Keine Mitarbeiter zugeordnet</p>
|
<p className="text-gray-500 dark:text-gray-400">Keine Mitarbeitenden zugeordnet</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { useAuthStore } from '../stores/authStore'
|
|||||||
const navigation = [
|
const navigation = [
|
||||||
{ name: 'Dashboard', href: '/', icon: HomeIcon },
|
{ name: 'Dashboard', href: '/', icon: HomeIcon },
|
||||||
{ name: 'Mein Profil', href: '/profile', icon: UsersIcon },
|
{ name: 'Mein Profil', href: '/profile', icon: UsersIcon },
|
||||||
{ name: 'Mitarbeiter', href: '/employees', icon: UsersIcon },
|
{ name: 'Mitarbeitende', href: '/employees', icon: UsersIcon },
|
||||||
{ name: 'Skill-Suche', href: '/search', icon: SearchIcon },
|
{ name: 'Skill-Suche', href: '/search', icon: SearchIcon },
|
||||||
{ name: 'Team-Zusammenstellung', href: '/team', icon: UsersIcon },
|
{ name: 'Team-Zusammenstellung', href: '/team', icon: UsersIcon },
|
||||||
{ name: 'Einstellungen', href: '/settings', icon: SettingsIcon },
|
{ name: 'Einstellungen', href: '/settings', icon: SettingsIcon },
|
||||||
|
|||||||
@ -61,7 +61,7 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
const statsCards = [
|
const statsCards = [
|
||||||
{
|
{
|
||||||
title: 'Mitarbeiter gesamt',
|
title: 'Mitarbeitende gesamt',
|
||||||
value: stats.totalEmployees,
|
value: stats.totalEmployees,
|
||||||
color: 'text-primary-blue',
|
color: 'text-primary-blue',
|
||||||
bgColor: 'bg-bg-accent',
|
bgColor: 'bg-bg-accent',
|
||||||
@ -200,7 +200,7 @@ export default function Dashboard() {
|
|||||||
<div className="card">
|
<div className="card">
|
||||||
<h2 className="text-title-card font-poppins font-semibold text-primary mb-4">Profilqualität</h2>
|
<h2 className="text-title-card font-poppins font-semibold text-primary mb-4">Profilqualität</h2>
|
||||||
{!user?.employeeId ? (
|
{!user?.employeeId ? (
|
||||||
<p className="text-tertiary">Kein Mitarbeiterprofil mit Ihrem Nutzer verknüpft.</p>
|
<p className="text-tertiary">Kein Mitarbeitendenprofil mit Ihrem Nutzer verknüpft.</p>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="w-full h-3 bg-bg-gray dark:bg-dark-primary rounded-input overflow-hidden">
|
<div className="w-full h-3 bg-bg-gray dark:bg-dark-primary rounded-input overflow-hidden">
|
||||||
@ -237,7 +237,7 @@ export default function Dashboard() {
|
|||||||
onClick={() => navigate('/employees')}
|
onClick={() => navigate('/employees')}
|
||||||
className="w-full btn-secondary"
|
className="w-full btn-secondary"
|
||||||
>
|
>
|
||||||
Mitarbeiter verwalten
|
Mitarbeitende verwalten
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/search')}
|
onClick={() => navigate('/search')}
|
||||||
|
|||||||
@ -84,12 +84,12 @@ export default function EmployeeDetail() {
|
|||||||
if (!employee) {
|
if (!employee) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<p className="text-tertiary">Mitarbeiter wird geladen...</p>
|
<p className="text-tertiary">Profil wird geladen...</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hinweis: Verfügbarkeits-Badge wird im Mitarbeiter-Detail nicht angezeigt
|
// Hinweis: Verfügbarkeits-Badge wird im Mitarbeitenden-Detail nicht angezeigt
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -175,7 +175,7 @@ export default function EmployeeForm() {
|
|||||||
|
|
||||||
if (!formData.firstName.trim()) errors.firstName = 'Vorname ist erforderlich'
|
if (!formData.firstName.trim()) errors.firstName = 'Vorname ist erforderlich'
|
||||||
if (!formData.lastName.trim()) errors.lastName = 'Nachname ist erforderlich'
|
if (!formData.lastName.trim()) errors.lastName = 'Nachname ist erforderlich'
|
||||||
if (!formData.employeeNumber.trim()) errors.employeeNumber = 'Mitarbeiternummer ist erforderlich'
|
if (!formData.employeeNumber.trim()) errors.employeeNumber = 'Personalnummer ist erforderlich'
|
||||||
if (!formData.position.trim()) errors.position = 'Position ist erforderlich'
|
if (!formData.position.trim()) errors.position = 'Position ist erforderlich'
|
||||||
if (!formData.department.trim()) errors.department = 'Abteilung ist erforderlich'
|
if (!formData.department.trim()) errors.department = 'Abteilung ist erforderlich'
|
||||||
if (!formData.email.trim()) errors.email = 'E-Mail ist erforderlich'
|
if (!formData.email.trim()) errors.email = 'E-Mail ist erforderlich'
|
||||||
@ -250,7 +250,7 @@ export default function EmployeeForm() {
|
|||||||
if (err.response?.status === 401) {
|
if (err.response?.status === 401) {
|
||||||
setError('Ihre Session ist abgelaufen. Bitte melden Sie sich erneut an.')
|
setError('Ihre Session ist abgelaufen. Bitte melden Sie sich erneut an.')
|
||||||
} else {
|
} else {
|
||||||
setError(err.response?.data?.message || 'Fehler beim Erstellen des Mitarbeiters')
|
setError(err.response?.data?.message || 'Fehler beim Erstellen des Profils')
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
@ -261,7 +261,7 @@ export default function EmployeeForm() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center mb-8">
|
<div className="flex justify-between items-center mb-8">
|
||||||
<h1 className="text-title-lg font-poppins font-bold text-primary">
|
<h1 className="text-title-lg font-poppins font-bold text-primary">
|
||||||
Neuer Mitarbeiter
|
Neues Mitarbeitendenprofil
|
||||||
</h1>
|
</h1>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/employees')}
|
onClick={() => navigate('/employees')}
|
||||||
@ -288,7 +288,7 @@ export default function EmployeeForm() {
|
|||||||
<div className="md:col-span-2 flex justify-center mb-6">
|
<div className="md:col-span-2 flex justify-center mb-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-secondary mb-2 text-center">
|
<label className="block text-sm font-medium text-secondary mb-2 text-center">
|
||||||
Mitarbeiterfoto
|
Profilfoto
|
||||||
</label>
|
</label>
|
||||||
<PhotoPreview
|
<PhotoPreview
|
||||||
currentPhoto={employeePhoto || undefined}
|
currentPhoto={employeePhoto || undefined}
|
||||||
@ -696,7 +696,7 @@ export default function EmployeeForm() {
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="btn-primary"
|
className="btn-primary"
|
||||||
>
|
>
|
||||||
{loading ? 'Wird gespeichert...' : 'Mitarbeiter erstellen'}
|
{loading ? 'Wird gespeichert...' : 'Profil erstellen'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -87,17 +87,17 @@ export default function EmployeeList() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center mb-8">
|
<div className="flex justify-between items-center mb-8">
|
||||||
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
|
||||||
Mitarbeiter & Expert:innen
|
Mitarbeitende & Expertise
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||||||
{filteredEmployees.length} von {employees.length} Mitarbeitern
|
{filteredEmployees.length} von {employees.length} Mitarbeitenden
|
||||||
</p>
|
</p>
|
||||||
{canCreateEmployee() && (
|
{canCreateEmployee() && (
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/employees/new')}
|
onClick={() => navigate('/employees/new')}
|
||||||
className="btn-primary"
|
className="btn-primary"
|
||||||
>
|
>
|
||||||
Mitarbeiter hinzufügen
|
Mitarbeitende hinzufügen
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -189,7 +189,7 @@ export default function EmployeeList() {
|
|||||||
|
|
||||||
{filteredEmployees.length === 0 && (
|
{filteredEmployees.length === 0 && (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<p className="text-tertiary">Keine Mitarbeiter gefunden</p>
|
<p className="text-tertiary">Keine Mitarbeitenden gefunden</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -142,7 +142,7 @@ export default function MyProfile() {
|
|||||||
const payload = {
|
const payload = {
|
||||||
firstName: form.firstName,
|
firstName: form.firstName,
|
||||||
lastName: form.lastName,
|
lastName: form.lastName,
|
||||||
position: form.position || 'Mitarbeiter',
|
position: form.position || 'Teammitglied',
|
||||||
department: form.department || '',
|
department: form.department || '',
|
||||||
employeeNumber: form.employeeNumber || undefined,
|
employeeNumber: form.employeeNumber || undefined,
|
||||||
email: user?.email || form.email,
|
email: user?.email || form.email,
|
||||||
@ -178,7 +178,7 @@ export default function MyProfile() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-title-lg font-poppins font-bold text-primary mb-4">Mein Profil</h1>
|
<h1 className="text-title-lg font-poppins font-bold text-primary mb-4">Mein Profil</h1>
|
||||||
<p className="text-tertiary">Kein Mitarbeiterprofil mit Ihrem Nutzer verknüpft.</p>
|
<p className="text-tertiary">Kein Mitarbeitendenprofil mit Ihrem Nutzer verknüpft.</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -117,8 +117,7 @@ export default function Settings() {
|
|||||||
<span className="font-medium">Entwickelt für:</span> Sicherheitsbehörden
|
<span className="font-medium">Entwickelt für:</span> Sicherheitsbehörden
|
||||||
</p>
|
</p>
|
||||||
<p className="text-small text-tertiary mt-4">
|
<p className="text-small text-tertiary mt-4">
|
||||||
SkillMate ist eine spezialisierte Anwendung zur Verwaltung von
|
SkillMate ist eine spezialisierte Anwendung zur Verwaltung von Fähigkeiten und Kompetenzen der Mitarbeitenden in Sicherheitsbehörden.
|
||||||
Mitarbeiterfähigkeiten und -kompetenzen in Sicherheitsbehörden.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -396,7 +396,7 @@ export default function SkillSearch() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-tertiary ml-auto">
|
<span className="text-xs text-tertiary ml-auto">
|
||||||
{skill.userCount} Mitarbeiter
|
{skill.userCount} Mitarbeitende
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
@ -530,7 +530,7 @@ export default function SkillSearch() {
|
|||||||
<div className="lg:col-span-1">
|
<div className="lg:col-span-1">
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h2 className="text-title-card font-poppins font-semibold text-primary mb-4">
|
<h2 className="text-title-card font-poppins font-semibold text-primary mb-4">
|
||||||
Mitarbeiter
|
Mitarbeitende
|
||||||
{filteredEmployees.length > 0 && (
|
{filteredEmployees.length > 0 && (
|
||||||
<span className="ml-2 text-body font-normal text-tertiary">
|
<span className="ml-2 text-body font-normal text-tertiary">
|
||||||
({filteredEmployees.length})
|
({filteredEmployees.length})
|
||||||
@ -547,7 +547,7 @@ export default function SkillSearch() {
|
|||||||
) : filteredEmployees.length === 0 ? (
|
) : filteredEmployees.length === 0 ? (
|
||||||
<div className="text-center py-8">
|
<div className="text-center py-8">
|
||||||
<p className="text-tertiary text-sm">
|
<p className="text-tertiary text-sm">
|
||||||
Keine Mitarbeiter gefunden
|
Keine Mitarbeitenden gefunden
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -418,7 +418,7 @@ export default function TeamZusammenstellung() {
|
|||||||
Team-Zusammenstellung
|
Team-Zusammenstellung
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-tertiary">
|
<p className="text-tertiary">
|
||||||
Wählen Sie Skills oder nutzen Sie die Filter, um passende Mitarbeiter zu finden und Ihrem Team zuzuweisen.
|
Wählen Sie Skills oder nutzen Sie die Filter, um passende Mitarbeitende zu finden und Ihrem Team zuzuweisen.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -629,8 +629,8 @@ export default function TeamZusammenstellung() {
|
|||||||
<div className="card">
|
<div className="card">
|
||||||
<h2 className="text-title-card font-poppins font-semibold text-primary mb-4">
|
<h2 className="text-title-card font-poppins font-semibold text-primary mb-4">
|
||||||
{selectedSkills.size > 0 || searchTerm || selectedDepartment || selectedPosition
|
{selectedSkills.size > 0 || searchTerm || selectedDepartment || selectedPosition
|
||||||
? `Gefilterte Mitarbeiter (${filteredEmployees.length})`
|
? `Gefilterte Mitarbeitende (${filteredEmployees.length})`
|
||||||
: `Mitarbeiter`
|
: `Mitarbeitende`
|
||||||
}
|
}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
@ -641,12 +641,12 @@ export default function TeamZusammenstellung() {
|
|||||||
Nutzen Sie die Filter oder wählen Sie Skills aus
|
Nutzen Sie die Filter oder wählen Sie Skills aus
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-tertiary">
|
<p className="text-sm text-tertiary">
|
||||||
Die passenden Mitarbeiter werden dann hier angezeigt
|
Die passenden Mitarbeitenden werden dann hier angezeigt
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : filteredEmployees.length === 0 ? (
|
) : filteredEmployees.length === 0 ? (
|
||||||
<p className="text-center text-tertiary py-8">
|
<p className="text-center text-tertiary py-8">
|
||||||
Keine passenden Mitarbeiter gefunden
|
Keine passenden Mitarbeitenden gefunden
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
filteredEmployees.map((employee: any) => {
|
filteredEmployees.map((employee: any) => {
|
||||||
@ -912,7 +912,7 @@ export default function TeamZusammenstellung() {
|
|||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<div className="py-2 text-center text-xs text-gray-400">
|
<div className="py-2 text-center text-xs text-gray-400">
|
||||||
Wählen Sie einen Mitarbeiter aus
|
Wählen Sie eine Person aus
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren