Führerschein und Sicherheitsüberprüfung Ja-Nein
Dieser Commit ist enthalten in:
@ -5,9 +5,9 @@
|
|||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
- **Path**: `A:\GiTea\SkillMate`
|
- **Path**: `A:\GiTea\SkillMate`
|
||||||
- **Files**: 189 files
|
- **Files**: 268 files
|
||||||
- **Size**: 4.6 MB
|
- **Size**: 5.7 MB
|
||||||
- **Last Modified**: 2025-09-29 01:39
|
- **Last Modified**: 2025-09-29 20:12
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
@ -84,6 +84,59 @@ backend/
|
|||||||
│ ├── skillmate.dev.encrypted.db
|
│ ├── skillmate.dev.encrypted.db
|
||||||
│ ├── skillmate.dev.encrypted.db-shm
|
│ ├── skillmate.dev.encrypted.db-shm
|
||||||
│ ├── skillmate.dev.encrypted.db-wal
|
│ ├── skillmate.dev.encrypted.db-wal
|
||||||
|
│ ├── dist/
|
||||||
|
│ │ ├── index.js
|
||||||
|
│ │ ├── index.js.map
|
||||||
|
│ │ ├── config/
|
||||||
|
│ │ │ ├── appConfig.js
|
||||||
|
│ │ │ ├── appConfig.js.map
|
||||||
|
│ │ │ ├── database.js
|
||||||
|
│ │ │ ├── database.js.map
|
||||||
|
│ │ │ ├── secureDatabase.js
|
||||||
|
│ │ │ └── secureDatabase.js.map
|
||||||
|
│ │ ├── middleware/
|
||||||
|
│ │ │ ├── auth.js
|
||||||
|
│ │ │ ├── auth.js.map
|
||||||
|
│ │ │ ├── errorHandler.js
|
||||||
|
│ │ │ ├── errorHandler.js.map
|
||||||
|
│ │ │ ├── roleAuth.js
|
||||||
|
│ │ │ └── roleAuth.js.map
|
||||||
|
│ │ ├── repositories/
|
||||||
|
│ │ │ ├── employeeRepository.js
|
||||||
|
│ │ │ └── employeeRepository.js.map
|
||||||
|
│ │ ├── routes/
|
||||||
|
│ │ │ ├── analytics.js
|
||||||
|
│ │ │ ├── analytics.js.map
|
||||||
|
│ │ │ ├── auth.js
|
||||||
|
│ │ │ ├── auth.js.map
|
||||||
|
│ │ │ ├── employeeOrganization.js
|
||||||
|
│ │ │ ├── employeeOrganization.js.map
|
||||||
|
│ │ │ ├── employees.js
|
||||||
|
│ │ │ ├── employees.js.map
|
||||||
|
│ │ │ ├── employeesSecure.js
|
||||||
|
│ │ │ └── employeesSecure.js.map
|
||||||
|
│ │ ├── services/
|
||||||
|
│ │ │ ├── auditService.js
|
||||||
|
│ │ │ ├── auditService.js.map
|
||||||
|
│ │ │ ├── emailService.js
|
||||||
|
│ │ │ ├── emailService.js.map
|
||||||
|
│ │ │ ├── encryption.js
|
||||||
|
│ │ │ ├── encryption.js.map
|
||||||
|
│ │ │ ├── reminderService.js
|
||||||
|
│ │ │ ├── reminderService.js.map
|
||||||
|
│ │ │ ├── skillSeeder.js
|
||||||
|
│ │ │ └── skillSeeder.js.map
|
||||||
|
│ │ ├── usecases/
|
||||||
|
│ │ │ ├── employees.js
|
||||||
|
│ │ │ ├── employees.js.map
|
||||||
|
│ │ │ ├── users.js
|
||||||
|
│ │ │ └── users.js.map
|
||||||
|
│ │ ├── utils/
|
||||||
|
│ │ │ ├── logger.js
|
||||||
|
│ │ │ └── logger.js.map
|
||||||
|
│ │ └── validation/
|
||||||
|
│ │ ├── employeeValidators.js
|
||||||
|
│ │ └── employeeValidators.js.map
|
||||||
│ ├── logs/
|
│ ├── logs/
|
||||||
│ │ ├── combined.log
|
│ │ ├── combined.log
|
||||||
│ │ └── error.log
|
│ │ └── error.log
|
||||||
@ -194,6 +247,8 @@ frontend/
|
|||||||
│ │ └── skills.ts
|
│ │ └── skills.ts
|
||||||
│ ├── types/
|
│ ├── types/
|
||||||
│ │ └── electron.d.ts
|
│ │ └── electron.d.ts
|
||||||
|
│ ├── utils/
|
||||||
|
│ │ └── skillRules.ts
|
||||||
│ └── views/
|
│ └── views/
|
||||||
│ ├── Dashboard.tsx
|
│ ├── Dashboard.tsx
|
||||||
│ ├── DeskBooking.tsx
|
│ ├── DeskBooking.tsx
|
||||||
@ -249,3 +304,4 @@ This project is managed with Claude Project Manager. To work with this project:
|
|||||||
- README updated on 2025-09-27 12:01:06
|
- README updated on 2025-09-27 12:01:06
|
||||||
- README updated on 2025-09-28 18:39:07
|
- README updated on 2025-09-28 18:39:07
|
||||||
- README updated on 2025-09-29 19:21:55
|
- README updated on 2025-09-29 19:21:55
|
||||||
|
- README updated on 2025-09-29 20:15:07
|
||||||
|
|||||||
7
frontend/src/utils/skillRules.ts
Normale Datei
7
frontend/src/utils/skillRules.ts
Normale Datei
@ -0,0 +1,7 @@
|
|||||||
|
export const BOOLEAN_SKILL_GROUPS = new Set([
|
||||||
|
'certifications.vehicles',
|
||||||
|
'certifications.security_clearance'
|
||||||
|
])
|
||||||
|
|
||||||
|
export const isBooleanSkill = (categoryId: string, subCategoryId: string) =>
|
||||||
|
BOOLEAN_SKILL_GROUPS.has(`${categoryId}.${subCategoryId}`)
|
||||||
@ -6,6 +6,7 @@ import SkillLevelBar from '../components/SkillLevelBar'
|
|||||||
import OfficeMapModal from '../components/OfficeMapModal'
|
import OfficeMapModal from '../components/OfficeMapModal'
|
||||||
import { employeeApi } from '../services/api'
|
import { employeeApi } from '../services/api'
|
||||||
import { useAuthStore } from '../stores/authStore'
|
import { useAuthStore } from '../stores/authStore'
|
||||||
|
import { isBooleanSkill } from '../utils/skillRules'
|
||||||
|
|
||||||
export default function EmployeeDetail() {
|
export default function EmployeeDetail() {
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
@ -210,12 +211,18 @@ export default function EmployeeDetail() {
|
|||||||
{selected.map((sk) => {
|
{selected.map((sk) => {
|
||||||
const info = employee.skills.find(es => es.id === sk.id)
|
const info = employee.skills.find(es => es.id === sk.id)
|
||||||
const levelVal = info?.level ? Number(info.level) : ''
|
const levelVal = info?.level ? Number(info.level) : ''
|
||||||
|
const booleanSkill = isBooleanSkill(cat.id, sub.id)
|
||||||
return (
|
return (
|
||||||
<li key={sk.id}>
|
<li key={sk.id}>
|
||||||
<div className="flex items-center justify-between mb-1">
|
<div className="flex items-center justify-between mb-1">
|
||||||
<span className="text-secondary">{sk.name}</span>
|
<span className="text-secondary">{sk.name}</span>
|
||||||
|
{booleanSkill && info && (
|
||||||
|
<span className="text-small font-medium text-green-700 dark:text-green-400">Ja</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{!booleanSkill && (
|
||||||
<SkillLevelBar value={levelVal as any} onChange={() => {}} disabled showHelp={false} />
|
<SkillLevelBar value={levelVal as any} onChange={() => {}} disabled showHelp={false} />
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { employeeApi } from '../services/api'
|
|||||||
import { SKILL_HIERARCHY, LANGUAGE_LEVELS } from '../data/skillCategories'
|
import { SKILL_HIERARCHY, LANGUAGE_LEVELS } from '../data/skillCategories'
|
||||||
import PhotoPreview from '../components/PhotoPreview'
|
import PhotoPreview from '../components/PhotoPreview'
|
||||||
import OrganizationSelector from '../components/OrganizationSelector'
|
import OrganizationSelector from '../components/OrganizationSelector'
|
||||||
|
import { isBooleanSkill } from '../utils/skillRules'
|
||||||
|
|
||||||
export default function EmployeeForm() {
|
export default function EmployeeForm() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -87,7 +88,7 @@ export default function EmployeeForm() {
|
|||||||
subCategoryId,
|
subCategoryId,
|
||||||
skillId,
|
skillId,
|
||||||
name: skillName,
|
name: skillName,
|
||||||
level: ''
|
level: isBooleanSkill(categoryId, subCategoryId) ? 'yes' : ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return { ...prev, skills }
|
return { ...prev, skills }
|
||||||
@ -95,6 +96,7 @@ export default function EmployeeForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleSkillLevelChange = (categoryId: string, subCategoryId: string, skillId: string, level: string) => {
|
const handleSkillLevelChange = (categoryId: string, subCategoryId: string, skillId: string, level: string) => {
|
||||||
|
if (isBooleanSkill(categoryId, subCategoryId)) return
|
||||||
setFormData(prev => {
|
setFormData(prev => {
|
||||||
const skills = [...prev.skills]
|
const skills = [...prev.skills]
|
||||||
const skill = skills.find(s =>
|
const skill = skills.find(s =>
|
||||||
@ -204,10 +206,10 @@ export default function EmployeeForm() {
|
|||||||
const newEmployee: Partial<Employee> = {
|
const newEmployee: Partial<Employee> = {
|
||||||
...formData,
|
...formData,
|
||||||
skills: formData.skills.map((skill, index) => ({
|
skills: formData.skills.map((skill, index) => ({
|
||||||
id: `skill-${index}`,
|
id: skill.skillId || `skill-${index}`,
|
||||||
name: skill.name,
|
name: skill.name,
|
||||||
category: skill.categoryId,
|
category: skill.categoryId,
|
||||||
level: skill.level || 3
|
level: isBooleanSkill(skill.categoryId, skill.subCategoryId) ? null : (skill.level || 'Basic')
|
||||||
})),
|
})),
|
||||||
languages: formData.skills
|
languages: formData.skills
|
||||||
.filter(s => s.subCategoryId === 'languages')
|
.filter(s => s.subCategoryId === 'languages')
|
||||||
@ -659,7 +661,9 @@ export default function EmployeeForm() {
|
|||||||
|
|
||||||
{/* Niveauauswahl */}
|
{/* Niveauauswahl */}
|
||||||
{isSkillSelected(category.id, subCategory.id, skill.id) && (
|
{isSkillSelected(category.id, subCategory.id, skill.id) && (
|
||||||
subCategory.id === 'languages' ? (
|
isBooleanSkill(category.id, subCategory.id) ? (
|
||||||
|
<span className="text-sm font-medium text-green-700 dark:text-green-400">Ja</span>
|
||||||
|
) : subCategory.id === 'languages' ? (
|
||||||
<select
|
<select
|
||||||
value={getSkillLevel(category.id, subCategory.id, skill.id)}
|
value={getSkillLevel(category.id, subCategory.id, skill.id)}
|
||||||
onChange={(e) => handleSkillLevelChange(category.id, subCategory.id, skill.id, e.target.value)}
|
onChange={(e) => handleSkillLevelChange(category.id, subCategory.id, skill.id, e.target.value)}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import PhotoUpload from '../components/PhotoUpload'
|
|||||||
import SkillLevelBar from '../components/SkillLevelBar'
|
import SkillLevelBar from '../components/SkillLevelBar'
|
||||||
import DeputyManagement from '../components/DeputyManagement'
|
import DeputyManagement from '../components/DeputyManagement'
|
||||||
import OrganizationSelector from '../components/OrganizationSelector'
|
import OrganizationSelector from '../components/OrganizationSelector'
|
||||||
|
import { isBooleanSkill } from '../utils/skillRules'
|
||||||
|
|
||||||
interface SkillSelection { categoryId: string; subCategoryId: string; skillId: string; name: string; level: string }
|
interface SkillSelection { categoryId: string; subCategoryId: string; skillId: string; name: string; level: string }
|
||||||
|
|
||||||
@ -53,12 +54,16 @@ const AVAILABILITY_OPTIONS = [
|
|||||||
const mapped: SkillSelection[] = (data.skills || []).map((s: any) => {
|
const mapped: SkillSelection[] = (data.skills || []).map((s: any) => {
|
||||||
const catStr = s.category || ''
|
const catStr = s.category || ''
|
||||||
const [catId, subId] = String(catStr).split('.')
|
const [catId, subId] = String(catStr).split('.')
|
||||||
|
const categoryId = catId || ''
|
||||||
|
const subCategoryId = subId || ''
|
||||||
|
const booleanSkill = isBooleanSkill(categoryId, subCategoryId)
|
||||||
|
const rawLevel = (s.level ?? '').toString()
|
||||||
return {
|
return {
|
||||||
categoryId: catId || '',
|
categoryId,
|
||||||
subCategoryId: subId || '',
|
subCategoryId,
|
||||||
skillId: s.id,
|
skillId: s.id,
|
||||||
name: s.name,
|
name: s.name,
|
||||||
level: (s.level || '').toString()
|
level: booleanSkill ? (rawLevel && rawLevel !== '0' ? '1' : '') : rawLevel
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
setSkills(mapped)
|
setSkills(mapped)
|
||||||
@ -102,13 +107,14 @@ const AVAILABILITY_OPTIONS = [
|
|||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
list.splice(idx, 1)
|
list.splice(idx, 1)
|
||||||
} else {
|
} else {
|
||||||
list.push({ categoryId, subCategoryId, skillId, name: skillName, level: '' })
|
list.push({ categoryId, subCategoryId, skillId, name: skillName, level: isBooleanSkill(categoryId, subCategoryId) ? '1' : '' })
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSkillLevelChange = (categoryId: string, subCategoryId: string, skillId: string, level: string) => {
|
const handleSkillLevelChange = (categoryId: string, subCategoryId: string, skillId: string, level: string) => {
|
||||||
|
if (isBooleanSkill(categoryId, subCategoryId)) return
|
||||||
setSkills(prev => prev.map(s => (s.categoryId === categoryId && s.subCategoryId === subCategoryId && s.skillId === skillId) ? { ...s, level } : s))
|
setSkills(prev => prev.map(s => (s.categoryId === categoryId && s.subCategoryId === subCategoryId && s.skillId === skillId) ? { ...s, level } : s))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +168,12 @@ const AVAILABILITY_OPTIONS = [
|
|||||||
mobile: form.mobile || null,
|
mobile: form.mobile || null,
|
||||||
office: form.office || null,
|
office: form.office || null,
|
||||||
availability: form.availability || 'available',
|
availability: form.availability || 'available',
|
||||||
skills: skills.map((s, i) => ({ id: s.skillId, name: s.name, category: s.categoryId, level: Number(s.level) || 3 })),
|
skills: skills.map(s => ({
|
||||||
|
id: s.skillId,
|
||||||
|
name: s.name,
|
||||||
|
category: s.categoryId,
|
||||||
|
level: isBooleanSkill(s.categoryId, s.subCategoryId) ? null : (Number(s.level) || 3)
|
||||||
|
})),
|
||||||
languages: form.languages || [],
|
languages: form.languages || [],
|
||||||
specializations: form.specializations || []
|
specializations: form.specializations || []
|
||||||
}
|
}
|
||||||
@ -327,29 +338,40 @@ const AVAILABILITY_OPTIONS = [
|
|||||||
<div key={`${category.id}-${sub.id}`} className="mb-3">
|
<div key={`${category.id}-${sub.id}`} className="mb-3">
|
||||||
<p className="text-small text-tertiary mb-2">{sub.name}</p>
|
<p className="text-small text-tertiary mb-2">{sub.name}</p>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||||
{sub.skills.map(skill => (
|
{sub.skills.map(skill => {
|
||||||
<div key={`${category.id}-${sub.id}-${skill.id}`} className={`p-2 border rounded-input ${isSkillSelected(category.id, sub.id, skill.id) ? 'border-primary-blue bg-bg-accent' : 'border-border-default'}`}>
|
const booleanSkill = isBooleanSkill(category.id, sub.id)
|
||||||
|
const selected = isSkillSelected(category.id, sub.id, skill.id)
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={`${category.id}-${sub.id}-${skill.id}`}
|
||||||
|
className={`p-2 border rounded-input ${selected ? 'border-primary-blue bg-bg-accent' : 'border-border-default'}`}
|
||||||
|
>
|
||||||
<label className="flex items-center justify-between">
|
<label className="flex items-center justify-between">
|
||||||
<span className="text-body text-secondary">
|
<span className="text-body text-secondary">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
checked={isSkillSelected(category.id, sub.id, skill.id)}
|
checked={selected}
|
||||||
onChange={() => handleSkillToggle(category.id, sub.id, skill.id, skill.name)}
|
onChange={() => handleSkillToggle(category.id, sub.id, skill.id, skill.name)}
|
||||||
/>
|
/>
|
||||||
{skill.name}
|
{skill.name}
|
||||||
</span>
|
</span>
|
||||||
{isSkillSelected(category.id, sub.id, skill.id) && (
|
{selected && (
|
||||||
|
booleanSkill ? (
|
||||||
|
<span className="ml-3 text-small font-medium text-green-700 dark:text-green-400">Ja</span>
|
||||||
|
) : (
|
||||||
<div className="ml-3 flex-1">
|
<div className="ml-3 flex-1">
|
||||||
<SkillLevelBar
|
<SkillLevelBar
|
||||||
value={Number(getSkillLevel(category.id, sub.id, skill.id)) || ''}
|
value={Number(getSkillLevel(category.id, sub.id, skill.id)) || ''}
|
||||||
onChange={(val) => handleSkillLevelChange(category.id, sub.id, skill.id, String(val))}
|
onChange={(val) => handleSkillLevelChange(category.id, sub.id, skill.id, String(val))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren