Skip to content

Commit 7dba4da

Browse files
committed
feat: add Settings page for job management and update job tabs in AppTopBar
1 parent c372668 commit 7dba4da

3 files changed

Lines changed: 560 additions & 163 deletions

File tree

app/components/AppTopBar.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ const jobTabs = computed(() => {
111111
{ label: 'Table', to: `${base}/candidates`, icon: Table2, exact: true },
112112
{ label: 'Application Form', to: `${base}/application-form`, icon: FileText, exact: true },
113113
{ label: 'AI Analysis', to: `${base}/ai-analysis`, icon: Sparkles, exact: true },
114+
{ label: 'Settings', to: `${base}/settings`, icon: Settings, exact: true },
114115
]
115116
})
116117

app/pages/dashboard/jobs/[id]/index.vue

Lines changed: 5 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
CheckCircle2, XCircle, AlertTriangle, ArrowUpDown, ListFilter,
88
Maximize2, Minimize2, Brain, Loader2,
99
} from 'lucide-vue-next'
10-
import { z } from 'zod'
1110
import { usePreviewReadOnly } from '~/composables/usePreviewReadOnly'
1211
import { APPLICATION_STATUS_TRANSITIONS, JOB_STATUS_TRANSITIONS, INTERVIEW_STATUS_TRANSITIONS } from '~~/shared/status-transitions'
1312
@@ -934,82 +933,6 @@ async function handleJobTransition(newStatus: string) {
934933
}
935934
}
936935
937-
// ─────────────────────────────────────────────
938-
// Edit Job modal
939-
// ─────────────────────────────────────────────
940-
941-
const showEditModal = ref(false)
942-
const editForm = ref({
943-
title: '',
944-
description: '',
945-
location: '',
946-
type: 'full_time' as string,
947-
})
948-
949-
const editSchema = z.object({
950-
title: z.string().min(1, 'Title is required').max(200),
951-
description: z.string().optional(),
952-
location: z.string().optional(),
953-
type: z.enum(['full_time', 'part_time', 'contract', 'internship']),
954-
})
955-
956-
const isSaving = ref(false)
957-
const editErrors = ref<Record<string, string>>({})
958-
959-
function startEdit() {
960-
if (!jobData.value) return
961-
editForm.value = {
962-
title: jobData.value.title,
963-
description: jobData.value.description ?? '',
964-
location: jobData.value.location ?? '',
965-
type: jobData.value.type,
966-
}
967-
editErrors.value = {}
968-
showEditModal.value = true
969-
showMoreMenu.value = false
970-
}
971-
972-
function cancelEdit() {
973-
showEditModal.value = false
974-
editErrors.value = {}
975-
}
976-
977-
async function handleSave() {
978-
const result = editSchema.safeParse(editForm.value)
979-
if (!result.success) {
980-
editErrors.value = {}
981-
for (const issue of result.error.issues) {
982-
const field = issue.path[0]?.toString()
983-
if (field) editErrors.value[field] = issue.message
984-
}
985-
return
986-
}
987-
editErrors.value = {}
988-
989-
isSaving.value = true
990-
try {
991-
await updateJob({
992-
title: editForm.value.title,
993-
description: editForm.value.description || undefined,
994-
location: editForm.value.location || undefined,
995-
type: editForm.value.type as any,
996-
})
997-
showEditModal.value = false
998-
} catch (err: any) {
999-
if (handlePreviewReadOnlyError(err)) return
1000-
toast.error('Failed to save changes', { message: err.data?.statusMessage, statusCode: err.data?.statusCode })
1001-
} finally {
1002-
isSaving.value = false
1003-
}
1004-
}
1005-
1006-
const typeOptions = [
1007-
{ value: 'full_time', label: 'Full-time' },
1008-
{ value: 'part_time', label: 'Part-time' },
1009-
{ value: 'contract', label: 'Contract' },
1010-
{ value: 'internship', label: 'Internship' },
1011-
]
1012-
1013936
// ─────────────────────────────────────────────
1014937
// Delete
1015938
// ─────────────────────────────────────────────
@@ -1264,13 +1187,14 @@ function closeDocPreview() {
12641187
v-if="showMoreMenu"
12651188
class="absolute right-0 top-full mt-1.5 z-50 w-52 rounded-xl border border-surface-200 dark:border-surface-700/80 bg-white dark:bg-surface-900 shadow-xl shadow-surface-900/5 dark:shadow-black/20 py-1.5 origin-top-right"
12661189
>
1267-
<button
1268-
class="flex w-full cursor-pointer items-center gap-2.5 px-3.5 py-2 text-sm text-surface-700 dark:text-surface-300 hover:bg-surface-50 dark:hover:bg-surface-800/80 transition-colors"
1269-
@click="startEdit"
1190+
<NuxtLink
1191+
:to="$localePath(`/dashboard/jobs/${jobId}/settings`)"
1192+
class="flex w-full items-center gap-2.5 px-3.5 py-2 text-sm text-surface-700 dark:text-surface-300 hover:bg-surface-50 dark:hover:bg-surface-800/80 transition-colors"
1193+
@click="showMoreMenu = false"
12701194
>
12711195
<Pencil class="size-3.5 text-surface-400" />
12721196
Edit Job
1273-
</button>
1197+
</NuxtLink>
12741198
<button
12751199
class="flex w-full cursor-pointer items-center gap-2.5 px-3.5 py-2 text-sm text-surface-700 dark:text-surface-300 hover:bg-surface-50 dark:hover:bg-surface-800/80 transition-colors sm:hidden"
12761200
@click="showApplyModal = true; showMoreMenu = false"
@@ -2361,88 +2285,6 @@ function closeDocPreview() {
23612285
<!-- MODALS -->
23622286
<!-- ═══════════════════════════════════════ -->
23632287

2364-
<!-- Edit Job Modal -->
2365-
<Teleport :to="teleportTarget">
2366-
<div v-if="showEditModal" class="fixed inset-0 z-50 flex items-center justify-center">
2367-
<div class="absolute inset-0 bg-black/40 backdrop-blur-sm" @click="cancelEdit" />
2368-
<div class="relative bg-white dark:bg-surface-900 rounded-2xl shadow-2xl shadow-surface-900/10 dark:shadow-black/30 ring-1 ring-surface-200/80 dark:ring-surface-700/60 p-6 max-w-lg w-full mx-4 max-h-[90vh] overflow-y-auto">
2369-
<h3 class="text-lg font-semibold text-surface-900 dark:text-surface-100 mb-4">Edit Job</h3>
2370-
2371-
<form class="space-y-4" @submit.prevent="handleSave">
2372-
<div>
2373-
<label for="edit-title" class="block text-sm font-medium text-surface-700 dark:text-surface-300 mb-1">
2374-
Title <span class="text-danger-500">*</span>
2375-
</label>
2376-
<input
2377-
id="edit-title"
2378-
v-model="editForm.title"
2379-
type="text"
2380-
class="w-full rounded-lg border px-3 py-2 text-sm text-surface-900 dark:text-surface-100 bg-white dark:bg-surface-800 placeholder:text-surface-400 focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-brand-500 transition-colors"
2381-
:class="editErrors.title ? 'border-danger-300' : 'border-surface-300 dark:border-surface-700'"
2382-
/>
2383-
<p v-if="editErrors.title" class="mt-1 text-xs text-danger-600 dark:text-danger-400">{{ editErrors.title }}</p>
2384-
</div>
2385-
2386-
<div>
2387-
<label for="edit-description" class="block text-sm font-medium text-surface-700 dark:text-surface-300 mb-1">
2388-
Description
2389-
</label>
2390-
<textarea
2391-
id="edit-description"
2392-
v-model="editForm.description"
2393-
rows="4"
2394-
class="w-full rounded-lg border border-surface-300 dark:border-surface-700 px-3 py-2 text-sm text-surface-900 dark:text-surface-100 bg-white dark:bg-surface-800 placeholder:text-surface-400 focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-brand-500 transition-colors"
2395-
/>
2396-
</div>
2397-
2398-
<div>
2399-
<label for="edit-location" class="block text-sm font-medium text-surface-700 dark:text-surface-300 mb-1">
2400-
Location
2401-
</label>
2402-
<input
2403-
id="edit-location"
2404-
v-model="editForm.location"
2405-
type="text"
2406-
class="w-full rounded-lg border border-surface-300 dark:border-surface-700 px-3 py-2 text-sm text-surface-900 dark:text-surface-100 bg-white dark:bg-surface-800 placeholder:text-surface-400 focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-brand-500 transition-colors"
2407-
/>
2408-
</div>
2409-
2410-
<div>
2411-
<label for="edit-type" class="block text-sm font-medium text-surface-700 dark:text-surface-300 mb-1">
2412-
Employment Type
2413-
</label>
2414-
<select
2415-
id="edit-type"
2416-
v-model="editForm.type"
2417-
class="w-full rounded-lg border border-surface-300 dark:border-surface-700 px-3 py-2 text-sm text-surface-900 dark:text-surface-100 focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-brand-500 transition-colors bg-white dark:bg-surface-800"
2418-
>
2419-
<option v-for="opt in typeOptions" :key="opt.value" :value="opt.value">
2420-
{{ opt.label }}
2421-
</option>
2422-
</select>
2423-
</div>
2424-
2425-
<div class="flex items-center justify-end gap-3 pt-2">
2426-
<button
2427-
type="button"
2428-
class="cursor-pointer rounded-lg border border-surface-300 dark:border-surface-700 px-4 py-2 text-sm font-medium text-surface-700 dark:text-surface-300 hover:bg-surface-50 dark:hover:bg-surface-800 transition-colors"
2429-
@click="cancelEdit"
2430-
>
2431-
Cancel
2432-
</button>
2433-
<button
2434-
type="submit"
2435-
:disabled="isSaving"
2436-
class="cursor-pointer rounded-lg bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
2437-
>
2438-
{{ isSaving ? 'Saving…' : 'Save Changes' }}
2439-
</button>
2440-
</div>
2441-
</form>
2442-
</div>
2443-
</div>
2444-
</Teleport>
2445-
24462288
<!-- Delete Job Confirm -->
24472289
<Teleport :to="teleportTarget">
24482290
<div v-if="showDeleteConfirm" class="fixed inset-0 z-50 flex items-center justify-center">

0 commit comments

Comments
 (0)