diff --git a/frontend/layouts/portal.vue b/frontend/layouts/portal.vue new file mode 100644 index 0000000..c54346b --- /dev/null +++ b/frontend/layouts/portal.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/frontend/middleware/auth.global.ts b/frontend/middleware/auth.global.ts index 1060a90..c63ab55 100644 --- a/frontend/middleware/auth.global.ts +++ b/frontend/middleware/auth.global.ts @@ -1,16 +1,26 @@ export default defineNuxtRouteMiddleware(async (to) => { - const auth = useAuthStore() - const isLogin = to.path === '/login' + const auth = useAuthStore() + const isLogin = to.path === '/login' - if (!auth.checked) { - await auth.ensureSession() - } + if (!auth.checked) { + await auth.ensureSession() + } - if (!isLogin && !auth.isAuthenticated) { - return navigateTo('/login') - } + if (!isLogin && !auth.isAuthenticated) { + return navigateTo('/login') + } - if (isLogin && auth.isAuthenticated) { - return navigateTo('/') - } + if (isLogin && auth.isAuthenticated) { + const isClient = auth.user?.roles?.includes('ROLE_CLIENT') ?? false + return navigateTo(isClient ? '/portal' : '/') + } + + // ROLE_CLIENT: redirect to /portal, block internal pages + if (auth.isAuthenticated && auth.user?.roles?.includes('ROLE_CLIENT')) { + const isPortalRoute = to.path.startsWith('/portal') + const isLoginRoute = to.path === '/login' + if (!isPortalRoute && !isLoginRoute) { + return navigateTo('/portal') + } + } }) diff --git a/frontend/pages/portal/index.vue b/frontend/pages/portal/index.vue new file mode 100644 index 0000000..f9e4e01 --- /dev/null +++ b/frontend/pages/portal/index.vue @@ -0,0 +1,87 @@ + + + diff --git a/frontend/pages/portal/projects/[id]/new-ticket.vue b/frontend/pages/portal/projects/[id]/new-ticket.vue new file mode 100644 index 0000000..d7016bd --- /dev/null +++ b/frontend/pages/portal/projects/[id]/new-ticket.vue @@ -0,0 +1,132 @@ + + + diff --git a/frontend/services/client-tickets.ts b/frontend/services/client-tickets.ts index 18480f9..efda4eb 100644 --- a/frontend/services/client-tickets.ts +++ b/frontend/services/client-tickets.ts @@ -1,30 +1,31 @@ -import type { ClientTicket, ClientTicketWrite } from './dto/client-ticket' +import type { ClientTicket, ClientTicketWrite, ClientTicketStatusUpdate } from './dto/client-ticket' import type { HydraCollection } from '~/utils/api' import { extractHydraMembers } from '~/utils/api' export function useClientTicketService() { const api = useApi() - async function getAll(params?: Record): Promise { - const data = await api.get>('/client_tickets', params) + async function getAll(params?: { project?: number; status?: string; submittedBy?: number }): Promise { + const query: Record = {} + if (params?.project) query.project = `/api/projects/${params.project}` + if (params?.status) query.status = params.status + if (params?.submittedBy) query.submittedBy = `/api/users/${params.submittedBy}` + const data = await api.get>('/client_tickets', query) return extractHydraMembers(data) } async function getById(id: number): Promise { - return await api.get(`/client_tickets/${id}`) + return api.get(`/client_tickets/${id}`) } - async function create(data: ClientTicketWrite): Promise { - return await api.post('/client_tickets', data as Record, { - toastSuccessKey: 'clientTicket.created', + async function create(payload: ClientTicketWrite): Promise { + return api.post('/client_tickets', payload as Record, { + toastSuccessKey: 'portal.ticketCreated', }) } - async function updateStatus(id: number, status: string, statusComment?: string): Promise { - return await api.patch(`/client_tickets/${id}`, { - status, - ...(statusComment ? { statusComment } : {}), - }, { + async function updateStatus(id: number, payload: ClientTicketStatusUpdate): Promise { + return api.patch(`/client_tickets/${id}`, payload as Record, { toastSuccessKey: 'clientTicket.statusUpdated', }) } diff --git a/frontend/services/dto/client-ticket.ts b/frontend/services/dto/client-ticket.ts index af00fb3..542f8bb 100644 --- a/frontend/services/dto/client-ticket.ts +++ b/frontend/services/dto/client-ticket.ts @@ -1,5 +1,4 @@ import type { TaskDocument } from './task-document' -import type { UserData } from './user-data' export type ClientTicketType = 'bug' | 'improvement' | 'other' export type ClientTicketStatus = 'new' | 'in_progress' | 'done' | 'rejected' @@ -15,10 +14,10 @@ export type ClientTicket = { status: ClientTicketStatus statusComment: string | null project: string - submittedBy: UserData | null + submittedBy: string | null createdAt: string updatedAt: string - documents: TaskDocument[] + documents?: TaskDocument[] } export type ClientTicketWrite = { @@ -28,3 +27,8 @@ export type ClientTicketWrite = { url?: string | null project: string } + +export type ClientTicketStatusUpdate = { + status: ClientTicketStatus + statusComment?: string | null +} diff --git a/frontend/services/dto/task.ts b/frontend/services/dto/task.ts index 770d579..abc2bc8 100644 --- a/frontend/services/dto/task.ts +++ b/frontend/services/dto/task.ts @@ -22,7 +22,13 @@ export type Task = { tags: TaskTag[] documents: TaskDocument[] archived: boolean - clientTicket?: { id: number; number: number; type: string; status: string; title: string } | null + clientTicket: { + id: number + number: number + type: string + status: string + title: string + } | null } export type TaskWrite = { diff --git a/frontend/services/dto/user-data.ts b/frontend/services/dto/user-data.ts index 0f618a8..c7d1163 100644 --- a/frontend/services/dto/user-data.ts +++ b/frontend/services/dto/user-data.ts @@ -1,10 +1,12 @@ +import type { Project } from './project' + export type UserData = { id: number '@id'?: string username: string roles: string[] - client?: { '@id'?: string; id: number; name: string } | null - allowedProjects?: { '@id'?: string; id: number; name: string }[] + client?: { id: number; name: string } | null + allowedProjects?: Project[] } export type UserWrite = {