diff --git a/app/composables/useApplications.ts b/app/composables/useApplications.ts index a5658da3..1eb5d473 100644 --- a/app/composables/useApplications.ts +++ b/app/composables/useApplications.ts @@ -9,6 +9,7 @@ export function useApplications(options?: { jobId?: Ref | string candidateId?: Ref | string status?: Ref | string + search?: Ref | string }) { const { handlePreviewReadOnlyError } = usePreviewReadOnly() @@ -16,6 +17,7 @@ export function useApplications(options?: { ...(toValue(options?.jobId) && { jobId: toValue(options?.jobId) }), ...(toValue(options?.candidateId) && { candidateId: toValue(options?.candidateId) }), ...(toValue(options?.status) && { status: toValue(options?.status) }), + ...(toValue(options?.search) && { search: toValue(options?.search) }), })) const { data, status: fetchStatus, error, refresh } = useFetch('/api/applications', { diff --git a/app/pages/dashboard/applications/index.vue b/app/pages/dashboard/applications/index.vue index 18108eef..93083578 100644 --- a/app/pages/dashboard/applications/index.vue +++ b/app/pages/dashboard/applications/index.vue @@ -1,5 +1,5 @@ diff --git a/railway.json b/railway.json index bf0bf24d..7b3266fe 100644 --- a/railway.json +++ b/railway.json @@ -1,5 +1,4 @@ { - "$schema": "https://railway.app/railway.schema.json", "deploy": { "preDeployCommand": "npm run db:push && npm run db:seed", "startCommand": "npm run start" diff --git a/server/api/applications/index.get.ts b/server/api/applications/index.get.ts index 9027c6bc..9b9cb81a 100644 --- a/server/api/applications/index.get.ts +++ b/server/api/applications/index.get.ts @@ -1,4 +1,4 @@ -import { eq, and, desc, sql } from 'drizzle-orm' +import { eq, and, desc, sql, ilike, or } from 'drizzle-orm' import { application, candidate, job } from '../../database/schema' import { applicationQuerySchema } from '../../utils/schemas/application' @@ -25,10 +25,21 @@ export default defineEventHandler(async (event) => { if (query.status) { conditions.push(eq(application.status, query.status)) } + if (query.search) { + const term = `%${query.search}%` + conditions.push( + or( + ilike(candidate.firstName, term), + ilike(candidate.lastName, term), + ilike(candidate.email, term), + ilike(job.title, term), + )!, + ) + } const where = and(...conditions) - const [data, total] = await Promise.all([ + const [data, countResult] = await Promise.all([ db .select({ id: application.id, @@ -43,6 +54,7 @@ export default defineEventHandler(async (event) => { candidateEmail: candidate.email, jobId: application.jobId, jobTitle: job.title, + jobLocation: job.location, jobStatus: job.status, }) .from(application) @@ -52,8 +64,15 @@ export default defineEventHandler(async (event) => { .orderBy(desc(application.createdAt)) .limit(query.limit) .offset(offset), - db.$count(application, where), + db + .select({ count: sql`count(*)::int` }) + .from(application) + .innerJoin(candidate, eq(candidate.id, application.candidateId)) + .innerJoin(job, eq(job.id, application.jobId)) + .where(where), ]) + const total = countResult[0]?.count ?? 0 + return { data, total, page: query.page, limit: query.limit } }) diff --git a/server/utils/schemas/application.ts b/server/utils/schemas/application.ts index 8f1adf55..73e117ce 100644 --- a/server/utils/schemas/application.ts +++ b/server/utils/schemas/application.ts @@ -25,6 +25,7 @@ export const applicationQuerySchema = z.object({ jobId: z.string().min(1).optional(), candidateId: z.string().min(1).optional(), status: z.enum(['new', 'screening', 'interview', 'offer', 'hired', 'rejected']).optional(), + search: z.string().max(200).optional(), }) /** Reusable schema for `:id` route params */