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

124
frontend/src/App.tsx Normal file
View File

@@ -0,0 +1,124 @@
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Toaster } from 'react-hot-toast';
import { useEffect } from 'react';
import { useAuthStore } from '@/store/authStore';
import { useConfigStore } from '@/store/configStore';
import { MainLayout } from '@/components/layout';
import { LoadingOverlay } from '@/components/ui';
import { Login } from '@/pages/Login';
import { Dashboard } from '@/pages/Dashboard';
import { CustomersList } from '@/pages/customers';
import { ProjectsList } from '@/pages/projects';
import { TasksList } from '@/pages/tasks';
import { EquipmentList } from '@/pages/equipment';
import { RMAList } from '@/pages/rma';
import { SettingsDashboard } from '@/pages/settings';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes
retry: 1,
},
},
});
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, user, isLoading, fetchProfile } = useAuthStore();
const { fetchConfig, isLoaded: configLoaded } = useConfigStore();
useEffect(() => {
// Only fetch profile if we have a token but no user data
const token = localStorage.getItem('accessToken');
if (token && !user) {
fetchProfile();
}
}, [user, fetchProfile]);
useEffect(() => {
if (isAuthenticated && !configLoaded) {
fetchConfig();
}
}, [isAuthenticated, configLoaded, fetchConfig]);
// Show loading only when we're actively fetching
if (isLoading && !user) {
return <LoadingOverlay message="Načítavam..." />;
}
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return <>{children}</>;
}
function RootOnlyRoute({ children }: { children: React.ReactNode }) {
const { user } = useAuthStore();
if (user?.role.code !== 'ROOT') {
return <Navigate to="/" replace />;
}
return <>{children}</>;
}
function AppRoutes() {
const { isAuthenticated } = useAuthStore();
return (
<Routes>
<Route
path="/login"
element={isAuthenticated ? <Navigate to="/" replace /> : <Login />}
/>
<Route
element={
<ProtectedRoute>
<MainLayout />
</ProtectedRoute>
}
>
<Route path="/" element={<Dashboard />} />
<Route path="/customers" element={<CustomersList />} />
<Route path="/projects" element={<ProjectsList />} />
<Route path="/tasks" element={<TasksList />} />
<Route path="/equipment" element={<EquipmentList />} />
<Route path="/rma" element={<RMAList />} />
<Route
path="/settings"
element={
<RootOnlyRoute>
<SettingsDashboard />
</RootOnlyRoute>
}
/>
</Route>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
);
}
export function App() {
return (
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<AppRoutes />
<Toaster
position="top-right"
toastOptions={{
duration: 3000,
style: {
background: '#fff',
color: '#0f172a',
border: '1px solid #e2e8f0',
},
}}
/>
</BrowserRouter>
</QueryClientProvider>
);
}