Initial commit: Helpdesk application setup

- Backend: Node.js/TypeScript with Prisma ORM
- Frontend: Vite + TypeScript
- Project configuration and documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 08:53:22 +01:00
commit e4f63a135e
103 changed files with 19913 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { User } from '@/types';
import { authApi, type LoginCredentials } from '@/services/auth.api';
interface AuthState {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
login: (credentials: LoginCredentials) => Promise<void>;
logout: () => Promise<void>;
fetchProfile: () => Promise<void>;
clearError: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
user: null,
isAuthenticated: false,
isLoading: false,
error: null,
login: async (credentials) => {
set({ isLoading: true, error: null });
try {
const response = await authApi.login(credentials);
const { user, accessToken, refreshToken } = response.data;
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
set({ user, isAuthenticated: true, isLoading: false });
} catch (err) {
const message = err instanceof Error ? err.message : 'Prihlásenie zlyhalo';
set({ error: message, isLoading: false });
throw err;
}
},
logout: async () => {
try {
await authApi.logout();
} catch {
// Ignore logout errors
} finally {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
set({ user: null, isAuthenticated: false });
}
},
fetchProfile: async () => {
const token = localStorage.getItem('accessToken');
if (!token) {
set({ user: null, isAuthenticated: false });
return;
}
set({ isLoading: true });
try {
const response = await authApi.getProfile();
set({ user: response.data, isAuthenticated: true, isLoading: false });
} catch {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
set({ user: null, isAuthenticated: false, isLoading: false });
}
},
clearError: () => set({ error: null }),
}),
{
name: 'auth-storage',
partialize: (state) => ({ user: state.user, isAuthenticated: state.isAuthenticated }),
}
)
);

View File

@@ -0,0 +1,91 @@
import { create } from 'zustand';
import type {
TaskStatus,
Priority,
EquipmentType,
RevisionType,
RMAStatus,
RMASolution,
UserRole,
} from '@/types';
import { settingsApi } from '@/services/settings.api';
interface ConfigState {
taskStatuses: TaskStatus[];
priorities: Priority[];
equipmentTypes: EquipmentType[];
revisionTypes: RevisionType[];
rmaStatuses: RMAStatus[];
rmaSolutions: RMASolution[];
userRoles: UserRole[];
isLoading: boolean;
isLoaded: boolean;
fetchConfig: () => Promise<void>;
getTaskStatusById: (id: string) => TaskStatus | undefined;
getPriorityById: (id: string) => Priority | undefined;
getEquipmentTypeById: (id: string) => EquipmentType | undefined;
getRevisionTypeById: (id: string) => RevisionType | undefined;
getRMAStatusById: (id: string) => RMAStatus | undefined;
getRMASolutionById: (id: string) => RMASolution | undefined;
getUserRoleById: (id: string) => UserRole | undefined;
}
export const useConfigStore = create<ConfigState>((set, get) => ({
taskStatuses: [],
priorities: [],
equipmentTypes: [],
revisionTypes: [],
rmaStatuses: [],
rmaSolutions: [],
userRoles: [],
isLoading: false,
isLoaded: false,
fetchConfig: async () => {
if (get().isLoaded) return;
set({ isLoading: true });
try {
const [
taskStatusesRes,
prioritiesRes,
equipmentTypesRes,
revisionTypesRes,
rmaStatusesRes,
rmaSolutionsRes,
userRolesRes,
] = await Promise.all([
settingsApi.getTaskStatuses(),
settingsApi.getPriorities(),
settingsApi.getEquipmentTypes(),
settingsApi.getRevisionTypes(),
settingsApi.getRMAStatuses(),
settingsApi.getRMASolutions(),
settingsApi.getUserRoles(),
]);
set({
taskStatuses: taskStatusesRes.data,
priorities: prioritiesRes.data,
equipmentTypes: equipmentTypesRes.data,
revisionTypes: revisionTypesRes.data,
rmaStatuses: rmaStatusesRes.data,
rmaSolutions: rmaSolutionsRes.data,
userRoles: userRolesRes.data,
isLoading: false,
isLoaded: true,
});
} catch {
set({ isLoading: false });
}
},
getTaskStatusById: (id) => get().taskStatuses.find((s) => s.id === id),
getPriorityById: (id) => get().priorities.find((p) => p.id === id),
getEquipmentTypeById: (id) => get().equipmentTypes.find((t) => t.id === id),
getRevisionTypeById: (id) => get().revisionTypes.find((t) => t.id === id),
getRMAStatusById: (id) => get().rmaStatuses.find((s) => s.id === id),
getRMASolutionById: (id) => get().rmaSolutions.find((s) => s.id === id),
getUserRoleById: (id) => get().userRoles.find((r) => r.id === id),
}));