feat: Space Booking System - MVP complet

Sistem web pentru rezervarea de birouri și săli de ședință
cu flux de aprobare administrativă.

Stack: FastAPI + Vue.js 3 + SQLite + TypeScript

Features implementate:
- Autentificare JWT + Self-registration cu email verification
- CRUD Spații, Utilizatori, Settings (Admin)
- Calendar interactiv (FullCalendar) cu drag-and-drop
- Creare rezervări cu validare (durată, program, overlap, max/zi)
- Rezervări recurente (săptămânal)
- Admin: aprobare/respingere/anulare cereri
- Admin: creare directă rezervări (bypass approval)
- Admin: editare orice rezervare
- User: editare/anulare rezervări proprii
- Notificări in-app (bell icon + dropdown)
- Notificări email (async SMTP cu BackgroundTasks)
- Jurnal acțiuni administrative (audit log)
- Rapoarte avansate (utilizare, top users, approval rate)
- Șabloane rezervări (booking templates)
- Atașamente fișiere (upload/download)
- Conflict warnings (verificare disponibilitate real-time)
- Integrare Google Calendar (OAuth2)
- Suport timezone (UTC storage + user preference)
- 225+ teste backend

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-02-09 17:51:29 +00:00
commit df4031d99c
113 changed files with 24491 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import Login from '@/views/Login.vue'
import Dashboard from '@/views/Dashboard.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
redirect: '/dashboard'
},
{
path: '/login',
name: 'Login',
component: Login,
meta: { requiresAuth: false }
},
{
path: '/register',
name: 'Register',
component: () => import('@/views/Register.vue'),
meta: { requiresAuth: false }
},
{
path: '/verify',
name: 'VerifyEmail',
component: () => import('@/views/VerifyEmail.vue'),
meta: { requiresAuth: false }
},
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard,
meta: { requiresAuth: true }
},
{
path: '/spaces',
name: 'Spaces',
component: () => import('@/views/Spaces.vue'),
meta: { requiresAuth: true }
},
{
path: '/spaces/:id',
name: 'SpaceDetail',
component: () => import('@/views/SpaceDetail.vue'),
meta: { requiresAuth: true }
},
{
path: '/my-bookings',
name: 'MyBookings',
component: () => import('@/views/MyBookings.vue'),
meta: { requiresAuth: true }
},
{
path: '/profile',
name: 'UserProfile',
component: () => import('@/views/UserProfile.vue'),
meta: { requiresAuth: true }
},
{
path: '/admin',
name: 'Admin',
component: () => import('@/views/Admin.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: '/users',
name: 'Users',
component: () => import('@/views/Users.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: '/admin/settings',
name: 'AdminSettings',
component: () => import('@/views/Settings.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: '/admin/pending',
name: 'AdminPending',
component: () => import('@/views/AdminPending.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: '/admin/audit-log',
name: 'AuditLog',
component: () => import('@/views/AuditLog.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: '/admin/reports',
name: 'AdminReports',
component: () => import('@/views/AdminReports.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
}
]
})
// Navigation guard
router.beforeEach((to, _from, next) => {
const authStore = useAuthStore()
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
next('/login')
} else if (to.meta.requiresAdmin && !authStore.isAdmin) {
next('/dashboard')
} else if (to.path === '/login' && authStore.isAuthenticated) {
next('/dashboard')
} else {
next()
}
})
export default router