Führerschein und Sicherheitsüberprüfung Ja-Nein
Dieser Commit ist enthalten in:
@ -5,9 +5,9 @@
|
||||
## Project Overview
|
||||
|
||||
- **Path**: `A:\GiTea\SkillMate`
|
||||
- **Files**: 189 files
|
||||
- **Size**: 4.6 MB
|
||||
- **Last Modified**: 2025-09-29 01:39
|
||||
- **Files**: 268 files
|
||||
- **Size**: 5.7 MB
|
||||
- **Last Modified**: 2025-09-29 20:12
|
||||
|
||||
## Technology Stack
|
||||
|
||||
@ -84,6 +84,59 @@ backend/
|
||||
│ ├── skillmate.dev.encrypted.db
|
||||
│ ├── skillmate.dev.encrypted.db-shm
|
||||
│ ├── 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/
|
||||
│ │ ├── combined.log
|
||||
│ │ └── error.log
|
||||
@ -194,6 +247,8 @@ frontend/
|
||||
│ │ └── skills.ts
|
||||
│ ├── types/
|
||||
│ │ └── electron.d.ts
|
||||
│ ├── utils/
|
||||
│ │ └── skillRules.ts
|
||||
│ └── views/
|
||||
│ ├── Dashboard.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-28 18:39:07
|
||||
- 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}`)
|
||||
@ -5,7 +5,8 @@ import type { Employee } from '@skillmate/shared'
|
||||
import SkillLevelBar from '../components/SkillLevelBar'
|
||||
import OfficeMapModal from '../components/OfficeMapModal'
|
||||
import { employeeApi } from '../services/api'
|
||||
import { useAuthStore } from '../stores/authStore'
|
||||
import { useAuthStore } from '../stores/authStore'
|
||||
import { isBooleanSkill } from '../utils/skillRules'
|
||||
|
||||
export default function EmployeeDetail() {
|
||||
const { id } = useParams()
|
||||
@ -210,12 +211,18 @@ export default function EmployeeDetail() {
|
||||
{selected.map((sk) => {
|
||||
const info = employee.skills.find(es => es.id === sk.id)
|
||||
const levelVal = info?.level ? Number(info.level) : ''
|
||||
const booleanSkill = isBooleanSkill(cat.id, sub.id)
|
||||
return (
|
||||
<li key={sk.id}>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<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>
|
||||
<SkillLevelBar value={levelVal as any} onChange={() => {}} disabled showHelp={false} />
|
||||
{!booleanSkill && (
|
||||
<SkillLevelBar value={levelVal as any} onChange={() => {}} disabled showHelp={false} />
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
|
||||
@ -5,6 +5,7 @@ import { employeeApi } from '../services/api'
|
||||
import { SKILL_HIERARCHY, LANGUAGE_LEVELS } from '../data/skillCategories'
|
||||
import PhotoPreview from '../components/PhotoPreview'
|
||||
import OrganizationSelector from '../components/OrganizationSelector'
|
||||
import { isBooleanSkill } from '../utils/skillRules'
|
||||
|
||||
export default function EmployeeForm() {
|
||||
const navigate = useNavigate()
|
||||
@ -87,7 +88,7 @@ export default function EmployeeForm() {
|
||||
subCategoryId,
|
||||
skillId,
|
||||
name: skillName,
|
||||
level: ''
|
||||
level: isBooleanSkill(categoryId, subCategoryId) ? 'yes' : ''
|
||||
})
|
||||
}
|
||||
return { ...prev, skills }
|
||||
@ -95,6 +96,7 @@ export default function EmployeeForm() {
|
||||
}
|
||||
|
||||
const handleSkillLevelChange = (categoryId: string, subCategoryId: string, skillId: string, level: string) => {
|
||||
if (isBooleanSkill(categoryId, subCategoryId)) return
|
||||
setFormData(prev => {
|
||||
const skills = [...prev.skills]
|
||||
const skill = skills.find(s =>
|
||||
@ -204,10 +206,10 @@ export default function EmployeeForm() {
|
||||
const newEmployee: Partial<Employee> = {
|
||||
...formData,
|
||||
skills: formData.skills.map((skill, index) => ({
|
||||
id: `skill-${index}`,
|
||||
id: skill.skillId || `skill-${index}`,
|
||||
name: skill.name,
|
||||
category: skill.categoryId,
|
||||
level: skill.level || 3
|
||||
level: isBooleanSkill(skill.categoryId, skill.subCategoryId) ? null : (skill.level || 'Basic')
|
||||
})),
|
||||
languages: formData.skills
|
||||
.filter(s => s.subCategoryId === 'languages')
|
||||
@ -659,7 +661,9 @@ export default function EmployeeForm() {
|
||||
|
||||
{/* Niveauauswahl */}
|
||||
{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
|
||||
value={getSkillLevel(category.id, subCategory.id, skill.id)}
|
||||
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 DeputyManagement from '../components/DeputyManagement'
|
||||
import OrganizationSelector from '../components/OrganizationSelector'
|
||||
import { isBooleanSkill } from '../utils/skillRules'
|
||||
|
||||
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 catStr = s.category || ''
|
||||
const [catId, subId] = String(catStr).split('.')
|
||||
const categoryId = catId || ''
|
||||
const subCategoryId = subId || ''
|
||||
const booleanSkill = isBooleanSkill(categoryId, subCategoryId)
|
||||
const rawLevel = (s.level ?? '').toString()
|
||||
return {
|
||||
categoryId: catId || '',
|
||||
subCategoryId: subId || '',
|
||||
categoryId,
|
||||
subCategoryId,
|
||||
skillId: s.id,
|
||||
name: s.name,
|
||||
level: (s.level || '').toString()
|
||||
level: booleanSkill ? (rawLevel && rawLevel !== '0' ? '1' : '') : rawLevel
|
||||
}
|
||||
})
|
||||
setSkills(mapped)
|
||||
@ -102,13 +107,14 @@ const AVAILABILITY_OPTIONS = [
|
||||
if (idx >= 0) {
|
||||
list.splice(idx, 1)
|
||||
} else {
|
||||
list.push({ categoryId, subCategoryId, skillId, name: skillName, level: '' })
|
||||
list.push({ categoryId, subCategoryId, skillId, name: skillName, level: isBooleanSkill(categoryId, subCategoryId) ? '1' : '' })
|
||||
}
|
||||
return list
|
||||
})
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
@ -162,7 +168,12 @@ const AVAILABILITY_OPTIONS = [
|
||||
mobile: form.mobile || null,
|
||||
office: form.office || null,
|
||||
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 || [],
|
||||
specializations: form.specializations || []
|
||||
}
|
||||
@ -327,29 +338,40 @@ const AVAILABILITY_OPTIONS = [
|
||||
<div key={`${category.id}-${sub.id}`} className="mb-3">
|
||||
<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">
|
||||
{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'}`}>
|
||||
<label className="flex items-center justify-between">
|
||||
<span className="text-body text-secondary">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="mr-2"
|
||||
checked={isSkillSelected(category.id, sub.id, skill.id)}
|
||||
onChange={() => handleSkillToggle(category.id, sub.id, skill.id, skill.name)}
|
||||
/>
|
||||
{skill.name}
|
||||
</span>
|
||||
{isSkillSelected(category.id, sub.id, skill.id) && (
|
||||
<div className="ml-3 flex-1">
|
||||
<SkillLevelBar
|
||||
value={Number(getSkillLevel(category.id, sub.id, skill.id)) || ''}
|
||||
onChange={(val) => handleSkillLevelChange(category.id, sub.id, skill.id, String(val))}
|
||||
{sub.skills.map(skill => {
|
||||
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">
|
||||
<span className="text-body text-secondary">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="mr-2"
|
||||
checked={selected}
|
||||
onChange={() => handleSkillToggle(category.id, sub.id, skill.id, skill.name)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
{skill.name}
|
||||
</span>
|
||||
{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">
|
||||
<SkillLevelBar
|
||||
value={Number(getSkillLevel(category.id, sub.id, skill.id)) || ''}
|
||||
onChange={(val) => handleSkillLevelChange(category.id, sub.id, skill.id, String(val))}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren