feat: complete tOS project with HR, LEAN, Dashboard and Integrations modules
Full enterprise web operating system including: - Next.js 14 frontend with App Router, i18n (DE/EN), shadcn/ui - NestJS 10 backend with Prisma, JWT auth, Swagger docs - Keycloak 24 SSO with role-based access control - HR module (employees, time tracking, absences, org chart) - LEAN module (3S planning, morning meeting SQCDM, skill matrix) - Integrations module (PlentyONE, Zulip, Todoist, FreeScout, Nextcloud, ecoDMS, GembaDocs) - Dashboard with customizable drag & drop widget grid - Docker Compose infrastructure (PostgreSQL 16, Redis 7, Keycloak 24) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
36
packages/shared/package.json
Normal file
36
packages/shared/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@tos/shared",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "Shared types, utilities, and constants for tOS",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./types": {
|
||||
"import": "./dist/types/index.mjs",
|
||||
"require": "./dist/types/index.js",
|
||||
"types": "./dist/types/index.d.ts"
|
||||
},
|
||||
"./utils": {
|
||||
"import": "./dist/utils/index.mjs",
|
||||
"require": "./dist/utils/index.js",
|
||||
"types": "./dist/utils/index.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"clean": "rm -rf dist .turbo"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.3.0"
|
||||
}
|
||||
}
|
||||
6
packages/shared/src/index.ts
Normal file
6
packages/shared/src/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* @tos/shared - Shared types, utilities, and constants for tOS
|
||||
*/
|
||||
|
||||
export * from './types';
|
||||
export * from './utils';
|
||||
111
packages/shared/src/types/auth.ts
Normal file
111
packages/shared/src/types/auth.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Authentication and authorization types
|
||||
*/
|
||||
|
||||
export interface AuthUser {
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
roles: string[];
|
||||
accessToken: string;
|
||||
refreshToken?: string;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
export interface TokenPayload {
|
||||
sub: string;
|
||||
email: string;
|
||||
given_name: string;
|
||||
family_name: string;
|
||||
realm_access?: {
|
||||
roles: string[];
|
||||
};
|
||||
resource_access?: {
|
||||
[clientId: string]: {
|
||||
roles: string[];
|
||||
};
|
||||
};
|
||||
iat: number;
|
||||
exp: number;
|
||||
iss: string;
|
||||
aud: string | string[];
|
||||
}
|
||||
|
||||
export interface LoginRequest {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenRequest {
|
||||
refreshToken: string;
|
||||
}
|
||||
|
||||
export interface AuthResponse {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
expiresIn: number;
|
||||
tokenType: 'Bearer';
|
||||
}
|
||||
|
||||
export const PERMISSIONS = {
|
||||
USERS_READ: 'users:read',
|
||||
USERS_CREATE: 'users:create',
|
||||
USERS_UPDATE: 'users:update',
|
||||
USERS_DELETE: 'users:delete',
|
||||
DEPARTMENTS_READ: 'departments:read',
|
||||
DEPARTMENTS_MANAGE: 'departments:manage',
|
||||
EMPLOYEES_READ_ALL: 'employees:read:all',
|
||||
EMPLOYEES_READ_DEPARTMENT: 'employees:read:department',
|
||||
EMPLOYEES_READ_OWN: 'employees:read:own',
|
||||
EMPLOYEES_MANAGE: 'employees:manage',
|
||||
TIME_READ_ALL: 'time:read:all',
|
||||
TIME_READ_DEPARTMENT: 'time:read:department',
|
||||
TIME_READ_OWN: 'time:read:own',
|
||||
TIME_MANAGE: 'time:manage',
|
||||
ABSENCES_READ_ALL: 'absences:read:all',
|
||||
ABSENCES_READ_DEPARTMENT: 'absences:read:department',
|
||||
ABSENCES_READ_OWN: 'absences:read:own',
|
||||
ABSENCES_APPROVE: 'absences:approve',
|
||||
ABSENCES_CREATE: 'absences:create',
|
||||
LEAN_READ: 'lean:read',
|
||||
LEAN_MANAGE: 'lean:manage',
|
||||
INTEGRATIONS_READ: 'integrations:read',
|
||||
INTEGRATIONS_MANAGE: 'integrations:manage',
|
||||
ADMIN_ACCESS: 'admin:access',
|
||||
ADMIN_SYSTEM: 'admin:system',
|
||||
} as const;
|
||||
|
||||
export type Permission = (typeof PERMISSIONS)[keyof typeof PERMISSIONS];
|
||||
|
||||
export const ROLE_PERMISSIONS: Record<string, Permission[]> = {
|
||||
admin: Object.values(PERMISSIONS),
|
||||
manager: [
|
||||
PERMISSIONS.USERS_READ,
|
||||
PERMISSIONS.DEPARTMENTS_READ,
|
||||
PERMISSIONS.EMPLOYEES_READ_ALL,
|
||||
PERMISSIONS.TIME_READ_ALL,
|
||||
PERMISSIONS.ABSENCES_READ_ALL,
|
||||
PERMISSIONS.ABSENCES_APPROVE,
|
||||
PERMISSIONS.LEAN_READ,
|
||||
PERMISSIONS.LEAN_MANAGE,
|
||||
PERMISSIONS.INTEGRATIONS_READ,
|
||||
],
|
||||
department_head: [
|
||||
PERMISSIONS.USERS_READ,
|
||||
PERMISSIONS.DEPARTMENTS_READ,
|
||||
PERMISSIONS.EMPLOYEES_READ_DEPARTMENT,
|
||||
PERMISSIONS.TIME_READ_DEPARTMENT,
|
||||
PERMISSIONS.ABSENCES_READ_DEPARTMENT,
|
||||
PERMISSIONS.ABSENCES_APPROVE,
|
||||
PERMISSIONS.LEAN_READ,
|
||||
PERMISSIONS.LEAN_MANAGE,
|
||||
],
|
||||
employee: [
|
||||
PERMISSIONS.EMPLOYEES_READ_OWN,
|
||||
PERMISSIONS.TIME_READ_OWN,
|
||||
PERMISSIONS.ABSENCES_READ_OWN,
|
||||
PERMISSIONS.ABSENCES_CREATE,
|
||||
PERMISSIONS.LEAN_READ,
|
||||
],
|
||||
};
|
||||
55
packages/shared/src/types/department.ts
Normal file
55
packages/shared/src/types/department.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Department-related types
|
||||
*/
|
||||
|
||||
export interface Department {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
code: string;
|
||||
parentId?: string;
|
||||
managerId?: string;
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export const DEPARTMENTS = [
|
||||
'sales',
|
||||
'accounting',
|
||||
'warehouse',
|
||||
'logistics',
|
||||
'engineering',
|
||||
'it',
|
||||
'executive',
|
||||
'executive_assistant',
|
||||
'hr',
|
||||
'procurement',
|
||||
] as const;
|
||||
|
||||
export type DepartmentCode = (typeof DEPARTMENTS)[number];
|
||||
|
||||
export interface DepartmentWithStats extends Department {
|
||||
employeeCount: number;
|
||||
manager?: {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateDepartmentDto {
|
||||
name: string;
|
||||
description?: string;
|
||||
code: string;
|
||||
parentId?: string;
|
||||
managerId?: string;
|
||||
}
|
||||
|
||||
export interface UpdateDepartmentDto {
|
||||
name?: string;
|
||||
description?: string;
|
||||
parentId?: string;
|
||||
managerId?: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
133
packages/shared/src/types/employee.ts
Normal file
133
packages/shared/src/types/employee.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Employee and HR-related types
|
||||
*/
|
||||
|
||||
export interface Employee {
|
||||
id: string;
|
||||
userId: string;
|
||||
employeeNumber: string;
|
||||
position: string;
|
||||
entryDate: Date;
|
||||
exitDate?: Date;
|
||||
contractType: ContractType;
|
||||
workingHours: number;
|
||||
departmentId: string;
|
||||
managerId?: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export type ContractType =
|
||||
| 'FULL_TIME'
|
||||
| 'PART_TIME'
|
||||
| 'MINI_JOB'
|
||||
| 'INTERN'
|
||||
| 'WORKING_STUDENT'
|
||||
| 'FREELANCE'
|
||||
| 'TEMPORARY';
|
||||
|
||||
export interface TimeEntry {
|
||||
id: string;
|
||||
employeeId: string;
|
||||
date: Date;
|
||||
clockIn: Date;
|
||||
clockOut?: Date;
|
||||
breakMinutes: number;
|
||||
type: TimeEntryType;
|
||||
note?: string;
|
||||
correctedById?: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export type TimeEntryType =
|
||||
| 'REGULAR'
|
||||
| 'OVERTIME'
|
||||
| 'HOLIDAY'
|
||||
| 'SICK'
|
||||
| 'VACATION'
|
||||
| 'CORRECTION';
|
||||
|
||||
export interface Absence {
|
||||
id: string;
|
||||
employeeId: string;
|
||||
type: AbsenceType;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
days: number;
|
||||
status: ApprovalStatus;
|
||||
approvedById?: string;
|
||||
approvedAt?: Date;
|
||||
note?: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export type AbsenceType =
|
||||
| 'VACATION'
|
||||
| 'SICK'
|
||||
| 'SICK_CHILD'
|
||||
| 'SPECIAL_LEAVE'
|
||||
| 'UNPAID_LEAVE'
|
||||
| 'PARENTAL_LEAVE'
|
||||
| 'HOME_OFFICE'
|
||||
| 'BUSINESS_TRIP'
|
||||
| 'TRAINING'
|
||||
| 'COMPENSATION';
|
||||
|
||||
export type ApprovalStatus = 'PENDING' | 'APPROVED' | 'REJECTED' | 'CANCELLED';
|
||||
|
||||
export interface EmployeeReview {
|
||||
id: string;
|
||||
employeeId: string;
|
||||
date: Date;
|
||||
type: ReviewType;
|
||||
notes?: string;
|
||||
goals?: ReviewGoal[];
|
||||
reviewerId: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export type ReviewType = 'PROBATION' | 'ANNUAL' | 'GOAL_SETTING' | 'FEEDBACK';
|
||||
|
||||
export interface ReviewGoal {
|
||||
title: string;
|
||||
description?: string;
|
||||
dueDate?: Date;
|
||||
status: 'open' | 'in_progress' | 'completed' | 'cancelled';
|
||||
}
|
||||
|
||||
export interface OnboardingTask {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
dueOffset: number;
|
||||
assignedTo?: string;
|
||||
departmentId?: string;
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface CreateTimeEntryDto {
|
||||
date: Date;
|
||||
clockIn: Date;
|
||||
clockOut?: Date;
|
||||
breakMinutes?: number;
|
||||
type?: TimeEntryType;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export interface CreateAbsenceDto {
|
||||
type: AbsenceType;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
days: number;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export interface UpdateAbsenceStatusDto {
|
||||
status: ApprovalStatus;
|
||||
note?: string;
|
||||
}
|
||||
8
packages/shared/src/types/index.ts
Normal file
8
packages/shared/src/types/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Shared TypeScript types for tOS
|
||||
*/
|
||||
|
||||
export * from './user';
|
||||
export * from './department';
|
||||
export * from './employee';
|
||||
export * from './auth';
|
||||
286
packages/shared/src/types/skill-matrix.ts
Normal file
286
packages/shared/src/types/skill-matrix.ts
Normal file
@@ -0,0 +1,286 @@
|
||||
/**
|
||||
* Skill Matrix types for tOS LEAN module
|
||||
*/
|
||||
|
||||
/**
|
||||
* Skill level numeric values (0-4)
|
||||
* Based on LEAN competency matrix standards
|
||||
*/
|
||||
export type SkillLevelValue = 0 | 1 | 2 | 3 | 4;
|
||||
|
||||
/**
|
||||
* Skill level labels
|
||||
*/
|
||||
export const SKILL_LEVEL_LABELS: Record<SkillLevelValue, string> = {
|
||||
0: 'Keine Kenntnisse',
|
||||
1: 'Grundlagen',
|
||||
2: 'Selbststaendig',
|
||||
3: 'Experte',
|
||||
4: 'Kann schulen',
|
||||
};
|
||||
|
||||
/**
|
||||
* Skill level labels in English
|
||||
*/
|
||||
export const SKILL_LEVEL_LABELS_EN: Record<SkillLevelValue, string> = {
|
||||
0: 'No knowledge',
|
||||
1: 'Basic understanding',
|
||||
2: 'Independent work',
|
||||
3: 'Expert',
|
||||
4: 'Can train others',
|
||||
};
|
||||
|
||||
/**
|
||||
* Skill level colors for visual display
|
||||
* Following LEAN traffic light convention
|
||||
*/
|
||||
export const SKILL_LEVEL_COLORS: Record<SkillLevelValue, string> = {
|
||||
0: 'bg-gray-200 text-gray-700',
|
||||
1: 'bg-red-500 text-white',
|
||||
2: 'bg-yellow-500 text-black',
|
||||
3: 'bg-green-500 text-white',
|
||||
4: 'bg-blue-500 text-white',
|
||||
};
|
||||
|
||||
/**
|
||||
* Skill level badge variants
|
||||
*/
|
||||
export const SKILL_LEVEL_VARIANTS: Record<SkillLevelValue, 'default' | 'secondary' | 'destructive' | 'warning' | 'success'> = {
|
||||
0: 'secondary',
|
||||
1: 'destructive',
|
||||
2: 'warning',
|
||||
3: 'success',
|
||||
4: 'default',
|
||||
};
|
||||
|
||||
/**
|
||||
* Skill categories
|
||||
*/
|
||||
export type SkillCategory = 'technical' | 'softSkills' | 'processes' | 'safety' | 'quality' | 'other';
|
||||
|
||||
/**
|
||||
* Skill category labels
|
||||
*/
|
||||
export const SKILL_CATEGORY_LABELS: Record<SkillCategory, string> = {
|
||||
technical: 'Technisch',
|
||||
softSkills: 'Soft Skills',
|
||||
processes: 'Prozesse',
|
||||
safety: 'Sicherheit',
|
||||
quality: 'Qualitaet',
|
||||
other: 'Sonstige',
|
||||
};
|
||||
|
||||
/**
|
||||
* Skill interface
|
||||
*/
|
||||
export interface Skill {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
category?: string | null;
|
||||
departmentId?: string | null;
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
department?: {
|
||||
id: string;
|
||||
name: string;
|
||||
code?: string | null;
|
||||
} | null;
|
||||
_count?: {
|
||||
entries: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill matrix entry (assessment)
|
||||
*/
|
||||
export interface SkillMatrixEntry {
|
||||
id: string;
|
||||
employeeId: string;
|
||||
skillId: string;
|
||||
level: SkillLevelValue;
|
||||
assessedAt: Date;
|
||||
assessedById?: string | null;
|
||||
notes?: string | null;
|
||||
nextReview?: Date | null;
|
||||
employee?: {
|
||||
id: string;
|
||||
employeeNumber: string;
|
||||
position: string;
|
||||
user: {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
};
|
||||
};
|
||||
skill?: Skill;
|
||||
assessedBy?: {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Employee skill data in matrix view
|
||||
*/
|
||||
export interface EmployeeSkillData {
|
||||
level: number;
|
||||
assessedAt: Date | null;
|
||||
assessedBy: {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
} | null;
|
||||
notes: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matrix row (employee with skills)
|
||||
*/
|
||||
export interface MatrixRow {
|
||||
employee: {
|
||||
id: string;
|
||||
employeeNumber: string;
|
||||
position: string;
|
||||
user: {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
};
|
||||
};
|
||||
skills: Record<string, EmployeeSkillData>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill matrix data structure
|
||||
*/
|
||||
export interface SkillMatrix {
|
||||
department: {
|
||||
id: string;
|
||||
name: string;
|
||||
code?: string | null;
|
||||
};
|
||||
skills: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
category?: string | null;
|
||||
isGlobal: boolean;
|
||||
}>;
|
||||
matrix: MatrixRow[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill gap analysis result
|
||||
*/
|
||||
export interface SkillGapAnalysis {
|
||||
skillId: string;
|
||||
skillName: string;
|
||||
category: string | null;
|
||||
targetLevel: number;
|
||||
averageLevel: number;
|
||||
gap: number;
|
||||
employeesBelow: number;
|
||||
employeesAtOrAbove: number;
|
||||
totalEmployees: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill coverage result
|
||||
*/
|
||||
export interface SkillCoverage {
|
||||
skillId: string;
|
||||
skillName: string;
|
||||
levelDistribution: Record<number, number>;
|
||||
totalAssessed: number;
|
||||
coverage: number;
|
||||
averageLevel: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trainer info for a skill
|
||||
*/
|
||||
export interface SkillTrainer {
|
||||
employeeId: string;
|
||||
employeeNumber: string;
|
||||
position: string;
|
||||
user: {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
department?: {
|
||||
id: string;
|
||||
name: string;
|
||||
} | null;
|
||||
};
|
||||
assessedAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trainers for a skill result
|
||||
*/
|
||||
export interface SkillTrainers {
|
||||
skill: {
|
||||
id: string;
|
||||
name: string;
|
||||
category: string | null;
|
||||
};
|
||||
trainers: SkillTrainer[];
|
||||
totalTrainers: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create skill DTO
|
||||
*/
|
||||
export interface CreateSkillDto {
|
||||
name: string;
|
||||
description?: string;
|
||||
category?: SkillCategory;
|
||||
departmentId?: string;
|
||||
targetLevel?: SkillLevelValue;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update skill DTO
|
||||
*/
|
||||
export interface UpdateSkillDto {
|
||||
name?: string;
|
||||
description?: string;
|
||||
category?: SkillCategory;
|
||||
departmentId?: string;
|
||||
targetLevel?: SkillLevelValue;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create skill entry DTO
|
||||
*/
|
||||
export interface CreateSkillEntryDto {
|
||||
employeeId: string;
|
||||
skillId: string;
|
||||
level: SkillLevelValue;
|
||||
notes?: string;
|
||||
nextReview?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update skill entry DTO
|
||||
*/
|
||||
export interface UpdateSkillEntryDto {
|
||||
level?: SkillLevelValue;
|
||||
notes?: string;
|
||||
nextReview?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk skill entry DTO
|
||||
*/
|
||||
export interface BulkSkillEntryDto {
|
||||
entries: CreateSkillEntryDto[];
|
||||
}
|
||||
57
packages/shared/src/types/user.ts
Normal file
57
packages/shared/src/types/user.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* User-related types
|
||||
*/
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
avatarUrl?: string;
|
||||
departmentId?: string;
|
||||
roles: UserRole[];
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export type UserRole = 'admin' | 'manager' | 'department_head' | 'employee';
|
||||
|
||||
export interface UserPreferences {
|
||||
theme: 'light' | 'dark' | 'system';
|
||||
language: 'de' | 'en';
|
||||
dashboardLayout?: DashboardWidgetConfig[];
|
||||
notifications: NotificationPreferences;
|
||||
}
|
||||
|
||||
export interface DashboardWidgetConfig {
|
||||
id: string;
|
||||
widgetType: string;
|
||||
position: { x: number; y: number };
|
||||
size: { width: number; height: number };
|
||||
settings?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface NotificationPreferences {
|
||||
email: boolean;
|
||||
push: boolean;
|
||||
inApp: boolean;
|
||||
digest: 'none' | 'daily' | 'weekly';
|
||||
}
|
||||
|
||||
export interface CreateUserDto {
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
departmentId?: string;
|
||||
roles: UserRole[];
|
||||
}
|
||||
|
||||
export interface UpdateUserDto {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
avatarUrl?: string;
|
||||
departmentId?: string;
|
||||
roles?: UserRole[];
|
||||
isActive?: boolean;
|
||||
}
|
||||
82
packages/shared/src/utils/constants.ts
Normal file
82
packages/shared/src/utils/constants.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Shared constants for tOS
|
||||
*/
|
||||
|
||||
export const APP_NAME = 'tOS';
|
||||
export const APP_DESCRIPTION = 'Enterprise Web Operating System';
|
||||
export const APP_VERSION = '0.1.0';
|
||||
|
||||
export const API_VERSION = 'v1';
|
||||
export const API_PREFIX = `/api/${API_VERSION}`;
|
||||
|
||||
export const DEFAULT_PAGE_SIZE = 20;
|
||||
export const MAX_PAGE_SIZE = 100;
|
||||
|
||||
// Time tracking configuration
|
||||
export const DEFAULT_BREAK_MINUTES = 30;
|
||||
export const MIN_BREAK_AFTER_HOURS = 6;
|
||||
export const REQUIRED_BREAK_MINUTES = 30;
|
||||
export const MAX_BREAK_AFTER_HOURS = 9;
|
||||
export const EXTENDED_BREAK_MINUTES = 45;
|
||||
|
||||
// Vacation configuration (German law defaults)
|
||||
export const DEFAULT_VACATION_DAYS = 30;
|
||||
export const MIN_VACATION_DAYS = 20;
|
||||
|
||||
// Working hours configuration
|
||||
export const STANDARD_WORKING_HOURS_PER_WEEK = 40;
|
||||
export const STANDARD_WORKING_HOURS_PER_DAY = 8;
|
||||
export const MAX_WORKING_HOURS_PER_DAY = 10;
|
||||
|
||||
// Skill matrix levels
|
||||
export const SKILL_LEVELS = {
|
||||
0: 'No knowledge',
|
||||
1: 'Basic understanding',
|
||||
2: 'Independent work',
|
||||
3: 'Expert',
|
||||
4: 'Can train others',
|
||||
} as const;
|
||||
|
||||
export type SkillLevel = keyof typeof SKILL_LEVELS;
|
||||
|
||||
// 3S categories (Seiri, Seiton, Seiso)
|
||||
export const S3_CATEGORIES = {
|
||||
SEIRI: 'Sort',
|
||||
SEITON: 'Set in Order',
|
||||
SEISO: 'Shine',
|
||||
} as const;
|
||||
|
||||
export type S3Category = keyof typeof S3_CATEGORIES;
|
||||
|
||||
// Morning meeting SQCDM categories
|
||||
export const SQCDM_CATEGORIES = {
|
||||
S: 'Safety',
|
||||
Q: 'Quality',
|
||||
C: 'Cost',
|
||||
D: 'Delivery',
|
||||
M: 'Morale',
|
||||
} as const;
|
||||
|
||||
export type SQCDMCategory = keyof typeof SQCDM_CATEGORIES;
|
||||
|
||||
export const HTTP_STATUS = {
|
||||
OK: 200,
|
||||
CREATED: 201,
|
||||
NO_CONTENT: 204,
|
||||
BAD_REQUEST: 400,
|
||||
UNAUTHORIZED: 401,
|
||||
FORBIDDEN: 403,
|
||||
NOT_FOUND: 404,
|
||||
CONFLICT: 409,
|
||||
UNPROCESSABLE_ENTITY: 422,
|
||||
INTERNAL_SERVER_ERROR: 500,
|
||||
SERVICE_UNAVAILABLE: 503,
|
||||
} as const;
|
||||
|
||||
export const DATE_FORMATS = {
|
||||
ISO: 'yyyy-MM-dd',
|
||||
DE_SHORT: 'dd.MM.yyyy',
|
||||
DE_LONG: 'dd. MMMM yyyy',
|
||||
TIME_24H: 'HH:mm',
|
||||
DATETIME_DE: 'dd.MM.yyyy HH:mm',
|
||||
} as const;
|
||||
115
packages/shared/src/utils/index.ts
Normal file
115
packages/shared/src/utils/index.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Shared utility functions for tOS
|
||||
*/
|
||||
|
||||
export * from './constants';
|
||||
|
||||
/**
|
||||
* Format a date to German locale string
|
||||
*/
|
||||
export function formatDate(
|
||||
date: Date | string,
|
||||
options?: Intl.DateTimeFormatOptions
|
||||
): string {
|
||||
const d = typeof date === 'string' ? new Date(date) : date;
|
||||
return d.toLocaleDateString(
|
||||
'de-DE',
|
||||
options ?? { day: '2-digit', month: '2-digit', year: 'numeric' }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a time to German locale string
|
||||
*/
|
||||
export function formatTime(
|
||||
date: Date | string,
|
||||
options?: Intl.DateTimeFormatOptions
|
||||
): string {
|
||||
const d = typeof date === 'string' ? new Date(date) : date;
|
||||
return d.toLocaleTimeString('de-DE', options ?? { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date and time to German locale string
|
||||
*/
|
||||
export function formatDateTime(date: Date | string): string {
|
||||
return `${formatDate(date)} ${formatTime(date)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate working days between two dates (excluding weekends)
|
||||
*/
|
||||
export function calculateWorkingDays(startDate: Date, endDate: Date): number {
|
||||
let count = 0;
|
||||
const current = new Date(startDate);
|
||||
|
||||
while (current <= endDate) {
|
||||
const dayOfWeek = current.getDay();
|
||||
if (dayOfWeek !== 0 && dayOfWeek !== 6) {
|
||||
count++;
|
||||
}
|
||||
current.setDate(current.getDate() + 1);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate hours and minutes from minutes
|
||||
*/
|
||||
export function minutesToHoursAndMinutes(totalMinutes: number): {
|
||||
hours: number;
|
||||
minutes: number;
|
||||
} {
|
||||
const hours = Math.floor(totalMinutes / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
return { hours, minutes };
|
||||
}
|
||||
|
||||
/**
|
||||
* Format minutes to "HH:MM" string
|
||||
*/
|
||||
export function formatMinutesToTime(totalMinutes: number): string {
|
||||
const { hours, minutes } = minutesToHoursAndMinutes(totalMinutes);
|
||||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get initials from first and last name
|
||||
*/
|
||||
export function getInitials(firstName: string, lastName: string): string {
|
||||
return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full name from first and last name
|
||||
*/
|
||||
export function getFullName(firstName: string, lastName: string): string {
|
||||
return `${firstName} ${lastName}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slugify a string
|
||||
*/
|
||||
export function slugify(str: string): string {
|
||||
return str
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^\w\s-]/g, '')
|
||||
.replace(/[\s_-]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is not null or undefined
|
||||
*/
|
||||
export function isDefined<T>(value: T | null | undefined): value is T {
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep for a specified number of milliseconds
|
||||
*/
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
22
packages/shared/tsconfig.json
Normal file
22
packages/shared/tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
11
packages/shared/tsup.config.ts
Normal file
11
packages/shared/tsup.config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts', 'src/types/index.ts', 'src/utils/index.ts'],
|
||||
format: ['cjs', 'esm'],
|
||||
dts: true,
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
treeshake: true,
|
||||
});
|
||||
Reference in New Issue
Block a user