diff --git a/src/routes/profiles.ts b/src/routes/profiles.ts index 021ef6b..bb535fb 100644 --- a/src/routes/profiles.ts +++ b/src/routes/profiles.ts @@ -2,36 +2,8 @@ import { FastifyInstance } from "fastify"; export default async function authProfilesRoutes(server: FastifyInstance) { // Ein einzelnes Profil laden (nur im aktuellen Tenant) - server.get<{ - Params: { id: string } - }>("/api/profiles/:id", { - schema: { - tags: ["Auth"], - summary: "Get a profile by user id (only from current tenant)", - params: { - type: "object", - properties: { - id: { type: "string", format: "uuid" } - }, - required: ["id"] - }, - response: { - 200: { - type: "object", - properties: { - id: { type: "string" }, - firstName: { type: "string" }, - lastName: { type: "string" }, - email: { type: "string", nullable: true }, - mobileTel: { type: "string", nullable: true }, - fixedTel: { type: "string", nullable: true }, - role: { type: "string", nullable: true } - } - } - } - } - }, async (req, reply) => { - const { id } = req.params; + server.get("/profiles/:id", async (req, reply) => { + const { id } = req.params as {id:string}; const tenantId = (req.user as any)?.tenant_id; if (!tenantId) { @@ -39,19 +11,44 @@ export default async function authProfilesRoutes(server: FastifyInstance) { } const { data, error } = await server.supabase - .from("auth_users") - .select("*, profile(*)") + .from("auth_profiles") + .select() .eq("id", id) - .eq("tenant", tenantId) + .eq("tenant_id", tenantId) .single(); if (error || !data) { + console.log(error) return reply.code(404).send({ error: "User not found or not in tenant" }); } - return { - user_id: data.id, - ...data.profile - }; + console.log(data); + + reply.send(data) }); + + server.put("/profiles/:id", async (req, reply) => { + if (!req.user.tenant_id) { + return reply.code(400).send({ error: "No tenant selected" }); + } + + const { id } = req.params as { id: string }; + const body = req.body + + delete body.full_name + + + const { data, error } = await server.supabase + .from("auth_profiles") + .update(body) + .eq("id", id) + .eq("tenant_id", req.user.tenant_id) + .select("*") + .single(); + + if (error || !data) { + console.log(error) + return reply.code(404).send({ error: "User not found or not in tenant" }); + } + }) } \ No newline at end of file diff --git a/src/routes/staff/time.ts b/src/routes/staff/time.ts new file mode 100644 index 0000000..4d437dd --- /dev/null +++ b/src/routes/staff/time.ts @@ -0,0 +1,135 @@ +import { FastifyInstance } from 'fastify' +import { StaffTimeEntry } from '../../types/staff' + +export default async function staffTimeRoutes(server: FastifyInstance) { + + // ▶ Neue Zeit starten + server.post<{ Body: Pick }>( + '/staff/time', + async (req, reply) => { + const { started_at, stopped_at, type = 'work', description } = req.body + const userId = req.user.user_id + const tenantId = req.user.tenant_id + + const { data, error } = await server.supabase + .from('staff_time_entries') + .insert([{ tenant_id: tenantId, user_id: userId, started_at, stopped_at, type, description }]) + .select() + .maybeSingle() + + if (error) return reply.code(400).send({ error: error.message }) + return reply.send(data) + } + ) + + // ▶ Zeit stoppen + server.put<{ Params: { id: string }, Body: { stopped_at: string } }>( + '/staff/time/:id/stop', + async (req, reply) => { + const { id } = req.params + const { stopped_at } = req.body + + const { data, error } = await server.supabase + .from('staff_time_entries') + .update({ stopped_at, updated_at: new Date().toISOString() }) + .eq('id', id) + .select() + .maybeSingle() + + if (error) return reply.code(400).send({ error: error.message }) + return reply.send(data) + } + ) + + // ▶ Liste aller Zeiten + server.get<{ + Querystring: { + from?: string + to?: string + type?: string + user_id?: string + } + }>('/staff/time', async (req, reply) => { + const { from, to, type, user_id } = req.query + const { user_id: currentUserId, tenant_id } = req.user + + // 🧩 Basis-Query für den Tenant + let query = server.supabase + .from('staff_time_entries') + .select('*') + .eq('tenant_id', tenant_id) + .order('started_at', { ascending: false }) + + // 🔒 Zugriffsbeschränkung: nur eigene Zeiten, außer Berechtigung erlaubt mehr + if (!req.hasPermission('staff.time.read_all')) { + query = query.eq('user_id', currentUserId) + } else if (user_id) { + // falls explizit user_id angegeben wurde + query = query.eq('user_id', user_id) + } + + // 📅 Zeitfilter + if (from) query = query.gte('started_at', from) + if (to) query = query.lte('started_at', to) + if (type) query = query.eq('type', type) + + const { data, error } = await query + if (error) return reply.code(400).send({ error: error.message }) + + return reply.send(data) + }) + + + // ▶ Einzelne Zeit abrufen (inkl. Connects) + server.get<{ Params: { id: string } }>( + '/staff/time/:id', + async (req, reply) => { + const { id } = req.params + + const { data, error } = await server.supabase + .from('staff_time_entries') + .select(` + *, + staff_time_entry_connects(*) + `) + .eq('id', id) + .maybeSingle() + + if (error) return reply.code(400).send({ error: error.message }) + return reply.send(data) + } + ) + + // ▶ Zeit bearbeiten + server.put<{ Params: { id: string }, Body: Partial }>( + '/staff/time/:id', + async (req, reply) => { + const { id } = req.params + + const { data, error } = await server.supabase + .from('staff_time_entries') + .update({ ...req.body, updated_at: new Date().toISOString() }) + .eq('id', id) + .select() + .maybeSingle() + + if (error) return reply.code(400).send({ error: error.message }) + return reply.send(data) + } + ) + + // ▶ Zeit löschen + server.delete<{ Params: { id: string } }>( + '/staff/time/:id', + async (req, reply) => { + const { id } = req.params + const { error } = await server.supabase + .from('staff_time_entries') + .delete() + .eq('id', id) + + if (error) return reply.code(400).send({ error: error.message }) + return reply.send({ success: true }) + } + ) +} diff --git a/src/routes/staff/timeconnects.ts b/src/routes/staff/timeconnects.ts new file mode 100644 index 0000000..288e341 --- /dev/null +++ b/src/routes/staff/timeconnects.ts @@ -0,0 +1,71 @@ +import { FastifyInstance } from 'fastify' +import { StaffTimeEntryConnect } from '../../types/staff' + +export default async function staffTimeConnectRoutes(server: FastifyInstance) { + + // ▶ Connect anlegen + server.post<{ Params: { id: string }, Body: Omit }>( + '/staff/time/:id/connects', + async (req, reply) => { + const { id } = req.params + const { started_at, stopped_at, project_id, customer_id, task_id, ticket_id, notes } = req.body + + const { data, error } = await server.supabase + .from('staff_time_entry_connects') + .insert([{ time_entry_id: id, started_at, stopped_at, project_id, customer_id, task_id, ticket_id, notes }]) + .select() + .maybeSingle() + + if (error) return reply.code(400).send({ error: error.message }) + return reply.send(data) + } + ) + + // ▶ Connects abrufen + server.get<{ Params: { id: string } }>( + '/staff/time/:id/connects', + async (req, reply) => { + const { id } = req.params + const { data, error } = await server.supabase + .from('staff_time_entry_connects') + .select('*') + .eq('time_entry_id', id) + .order('started_at', { ascending: true }) + + if (error) return reply.code(400).send({ error: error.message }) + return reply.send(data) + } + ) + + // ▶ Connect aktualisieren + server.patch<{ Params: { connectId: string }, Body: Partial }>( + '/staff/time/connects/:connectId', + async (req, reply) => { + const { connectId } = req.params + const { data, error } = await server.supabase + .from('staff_time_entry_connects') + .update({ ...req.body, updated_at: new Date().toISOString() }) + .eq('id', connectId) + .select() + .maybeSingle() + + if (error) return reply.code(400).send({ error: error.message }) + return reply.send(data) + } + ) + + // ▶ Connect löschen + server.delete<{ Params: { connectId: string } }>( + '/staff/time/connects/:connectId', + async (req, reply) => { + const { connectId } = req.params + const { error } = await server.supabase + .from('staff_time_entry_connects') + .delete() + .eq('id', connectId) + + if (error) return reply.code(400).send({ error: error.message }) + return reply.send({ success: true }) + } + ) +} diff --git a/src/types/staff.ts b/src/types/staff.ts new file mode 100644 index 0000000..b16ae42 --- /dev/null +++ b/src/types/staff.ts @@ -0,0 +1,27 @@ +export interface StaffTimeEntry { + id: string + tenant_id: string + user_id: string + started_at: string + stopped_at?: string | null + duration_minutes?: number | null + type: 'work' | 'break' | 'absence' | 'other' + description?: string | null + created_at?: string + updated_at?: string +} + +export interface StaffTimeEntryConnect { + id: string + time_entry_id: string + project_id?: string | null + customer_id?: string | null + task_id?: string | null + ticket_id?: string | null + started_at: string + stopped_at: string + duration_minutes?: number + notes?: string | null + created_at?: string + updated_at?: string +}