update position page design
This commit is contained in:
@@ -5,6 +5,7 @@ import { useCreateJobPosting, useUpdateJobPosting, useGenerateJobDescription, us
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { ROLE_CATEGORIES } from '@/lib/roleCategories';
|
||||
import { CERT_OPTIONS, CRITERIA_LABELS, ENGLISH_LEVELS, defaultPosition } from '@/lib/positionModel';
|
||||
import { SkillRequirementsField } from '@/components/krow/SkillRequirementsField';
|
||||
|
||||
/**
|
||||
* Create a Position.
|
||||
@@ -133,8 +134,8 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
|
||||
: 'bg-white border border-[#E5E7EB] rounded-2xl p-6 md:p-8 space-y-6'}>
|
||||
{/* Section Header */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-7 h-7 rounded-lg bg-[#EFF6FF] flex items-center justify-center">
|
||||
<Sparkles className="w-4 h-4 text-[#0A39DF]" />
|
||||
<div className="w-7 h-7 rounded-lg bg-[#EEF3FE] flex items-center justify-center">
|
||||
<Sparkles className="w-4 h-4 text-[#0838E0]" />
|
||||
</div>
|
||||
<h2 className="text-[16px] font-semibold text-[#111827]">Create Job Posting</h2>
|
||||
</div>
|
||||
@@ -143,19 +144,19 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-[13px] font-medium text-[#374151]">Role Category *</label>
|
||||
<select value={form.role_category} onChange={(e) => update('role_category', e.target.value)} className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none">
|
||||
<select value={form.role_category} onChange={(e) => update('role_category', e.target.value)} className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0838E0] focus:outline-none">
|
||||
{allCategories.map(r => <option key={r} value={r}>{r}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[13px] font-medium text-[#374151]">Job Title *</label>
|
||||
<input value={form.title} onChange={(e) => update('title', e.target.value)} placeholder="e.g. Experienced Server" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
|
||||
<input value={form.title} onChange={(e) => update('title', e.target.value)} placeholder="e.g. Experienced Server" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Add new category */}
|
||||
<div className="flex gap-2">
|
||||
<input value={newCategory} onChange={(e) => setNewCategory(e.target.value)} placeholder="Create a new role category (saved for reuse)..." className="flex-1 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
|
||||
<input value={newCategory} onChange={(e) => setNewCategory(e.target.value)} placeholder="Create a new role category (saved for reuse)..." className="flex-1 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (newCategory.trim()) {
|
||||
@@ -167,7 +168,7 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
|
||||
}
|
||||
}}
|
||||
disabled={createRoleCategory.isPending || !newCategory.trim()}
|
||||
className="px-4 bg-[#0A39DF] text-white text-[13px] font-medium rounded-lg hover:bg-[#082EB4] disabled:opacity-50 whitespace-nowrap"
|
||||
className="px-4 bg-[#0838E0] text-white text-[13px] font-medium rounded-lg hover:bg-[#062BAF] disabled:opacity-50 whitespace-nowrap"
|
||||
>
|
||||
+ Add
|
||||
</button>
|
||||
@@ -176,22 +177,22 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
|
||||
{/* Requirements */}
|
||||
<div>
|
||||
<label className="text-[13px] font-medium text-[#374151]">Custom Requirements (AI will use this)</label>
|
||||
<textarea value={form.custom_requirements} onChange={(e) => update('custom_requirements', e.target.value)} placeholder="e.g. Must have fine dining experience..." className="w-full mt-1.5 min-h-[70px] p-3 text-[13px] rounded-lg border border-[#E5E7EB] focus:border-[#0A39DF] focus:outline-none resize-none" />
|
||||
<textarea value={form.custom_requirements} onChange={(e) => update('custom_requirements', e.target.value)} placeholder="e.g. Must have fine dining experience..." className="w-full mt-1.5 min-h-[70px] p-3 text-[13px] rounded-lg border border-[#E5E7EB] focus:border-[#0838E0] focus:outline-none resize-none" />
|
||||
</div>
|
||||
|
||||
{/* Physical / Leadership / Attendance Expectations */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-[13px] font-medium text-[#374151]">Physical Requirements</label>
|
||||
<input value={form.physical_requirements} onChange={(e) => update('physical_requirements', e.target.value)} placeholder="e.g. Lift 50 lbs, stand 8 hrs" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
|
||||
<input value={form.physical_requirements} onChange={(e) => update('physical_requirements', e.target.value)} placeholder="e.g. Lift 50 lbs, stand 8 hrs" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[13px] font-medium text-[#374151]">Leadership Expectations</label>
|
||||
<input value={form.leadership_expectations} onChange={(e) => update('leadership_expectations', e.target.value)} placeholder="e.g. Lead a team of 5" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
|
||||
<input value={form.leadership_expectations} onChange={(e) => update('leadership_expectations', e.target.value)} placeholder="e.g. Lead a team of 5" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[13px] font-medium text-[#374151]">Attendance Expectations</label>
|
||||
<input value={form.attendance_expectations} onChange={(e) => update('attendance_expectations', e.target.value)} placeholder="e.g. On-time, no weekends off" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
|
||||
<input value={form.attendance_expectations} onChange={(e) => update('attendance_expectations', e.target.value)} placeholder="e.g. On-time, no weekends off" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -199,17 +200,17 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-[13px] font-medium text-[#374151]">Min. Experience (yrs)</label>
|
||||
<input type="number" min="0" value={form.min_experience_years} onChange={(e) => update('min_experience_years', parseInt(e.target.value) || 0)} className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
|
||||
<input type="number" min="0" value={form.min_experience_years} onChange={(e) => update('min_experience_years', parseInt(e.target.value) || 0)} className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[13px] font-medium text-[#374151]">English Level</label>
|
||||
<select value={form.english_required} onChange={(e) => update('english_required', e.target.value)} className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none">
|
||||
<select value={form.english_required} onChange={(e) => update('english_required', e.target.value)} className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0838E0] focus:outline-none">
|
||||
{ENGLISH_LEVELS.map(l => <option key={l.value} value={l.value}>{l.label}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[13px] font-medium text-[#374151]">Location</label>
|
||||
<input value={form.location} onChange={(e) => update('location', e.target.value)} placeholder="Bay Area, LA..." className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
|
||||
<input value={form.location} onChange={(e) => update('location', e.target.value)} placeholder="Bay Area, LA..." className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -217,11 +218,11 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-[13px] font-medium text-[#374151]">Pay Min ($/hr)</label>
|
||||
<input type="number" min="0" value={form.pay_range_min} onChange={(e) => update('pay_range_min', e.target.value)} placeholder="e.g. 18" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
|
||||
<input type="number" min="0" value={form.pay_range_min} onChange={(e) => update('pay_range_min', e.target.value)} placeholder="e.g. 18" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[13px] font-medium text-[#374151]">Pay Max ($/hr)</label>
|
||||
<input type="number" min="0" value={form.pay_range_max} onChange={(e) => update('pay_range_max', e.target.value)} placeholder="e.g. 28" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
|
||||
<input type="number" min="0" value={form.pay_range_max} onChange={(e) => update('pay_range_max', e.target.value)} placeholder="e.g. 28" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -235,7 +236,7 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
|
||||
type="button"
|
||||
onClick={() => toggleCert(cert)}
|
||||
className={`px-3 py-1.5 rounded-full text-[12px] font-medium border transition-all ${
|
||||
form.certifications_required.includes(cert) ? 'bg-[#0A39DF] border-[#0A39DF] text-white' : 'border-[#E5E7EB] text-[#6B7280] hover:border-[#D1D5DB]'
|
||||
form.certifications_required.includes(cert) ? 'bg-[#0838E0] border-[#0838E0] text-white' : 'border-[#E5E7EB] text-[#6B7280] hover:border-[#D1D5DB]'
|
||||
}`}
|
||||
>
|
||||
{cert}
|
||||
@@ -244,11 +245,19 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Required skills — what the matching engine reads. Placed next to the
|
||||
certifications it sits beside conceptually: both are "what a person
|
||||
must hold", one asserted on a résumé and one verified in Forge. */}
|
||||
<SkillRequirementsField
|
||||
value={form.skill_requirements}
|
||||
onChange={(next) => update('skill_requirements', next)}
|
||||
/>
|
||||
|
||||
{/* AI Generate Button */}
|
||||
<button
|
||||
onClick={handleGenerateDescription}
|
||||
disabled={generateDesc.isPending || createJob.isPending}
|
||||
className="w-full bg-[#0A39DF] hover:bg-[#082EB4] disabled:opacity-60 text-white font-medium py-3 rounded-xl text-[14px] flex items-center justify-center gap-2 transition-colors"
|
||||
className="w-full bg-[#0838E0] hover:bg-[#062BAF] disabled:opacity-60 text-white font-medium py-3 rounded-xl text-[14px] flex items-center justify-center gap-2 transition-colors"
|
||||
>
|
||||
{generateDesc.isPending || createJob.isPending ? (
|
||||
<><Loader2 className="w-4 h-4 animate-spin" /> Generating...</>
|
||||
@@ -259,7 +268,7 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
|
||||
|
||||
{/* AI Result Preview */}
|
||||
{aiResult && (
|
||||
<div className="rounded-xl border border-[#DBEAFE] bg-[#EFF6FF] p-4 space-y-3">
|
||||
<div className="rounded-xl border border-[#DBEAFE] bg-[#EEF3FE] p-4 space-y-3">
|
||||
<div className="flex items-center gap-1.5 text-[#1E40AF] text-[13px] font-medium">
|
||||
<Sparkles className="w-3.5 h-3.5" /> AI-Generated Description
|
||||
</div>
|
||||
@@ -292,7 +301,7 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
|
||||
<label className="text-[12px] text-[#374151]">{CRITERIA_LABELS[key]}</label>
|
||||
<span className={`text-[12px] font-medium ${totalWeight === 100 ? 'text-[#059669]' : 'text-[#F59E0B]'}`}>{val}%</span>
|
||||
</div>
|
||||
<input type="range" min="0" max="100" value={val} onChange={(e) => updateWeight(key, e.target.value)} className="w-full accent-[#0A39DF]" />
|
||||
<input type="range" min="0" max="100" value={val} onChange={(e) => updateWeight(key, e.target.value)} className="w-full accent-[#0838E0]" />
|
||||
</div>
|
||||
))}
|
||||
<div className="md:col-span-2 text-[12px] text-[#6B7280]">
|
||||
@@ -308,7 +317,7 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
|
||||
<button onClick={() => handleSave('draft')} disabled={createJob.isPending || updateJob.isPending} className="border border-[#E5E7EB] hover:bg-[#F9FAFB] text-[#374151] font-medium py-3 rounded-xl text-[14px] transition-colors disabled:opacity-50">
|
||||
Save as Draft
|
||||
</button>
|
||||
<button onClick={() => handleSave('active')} disabled={createJob.isPending || updateJob.isPending} className="bg-[#0A39DF] hover:bg-[#082EB4] text-white font-medium py-3 rounded-xl text-[14px] transition-colors disabled:opacity-50">
|
||||
<button onClick={() => handleSave('active')} disabled={createJob.isPending || updateJob.isPending} className="bg-[#0838E0] hover:bg-[#062BAF] text-white font-medium py-3 rounded-xl text-[14px] transition-colors disabled:opacity-50">
|
||||
Publish Job Posting
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user