update position page design
This commit is contained in:
@@ -6,6 +6,10 @@ import { MessageSquare, Phone, Calendar, X, Trash2, ChevronDown, UserCheck, Brie
|
||||
import { Image } from '@/components/ui/image';
|
||||
import { getScoreBand } from '@/lib/talentHome';
|
||||
import CandidateExpandedDetails from '@/components/krow/CandidateExpandedDetails';
|
||||
import ScheduleInterviewModal from '@/components/krow/ScheduleInterviewModal';
|
||||
import MessageCandidateModal from '@/components/krow/MessageCandidateModal';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { base44 } from '@/api/base44Client';
|
||||
|
||||
const STATUS_STYLES = {
|
||||
hired: { badge: 'bg-[#E8F0FE] text-[#1A56DB]', label: 'Hired', card: 'bg-white border-[#E5E7EB]' },
|
||||
@@ -40,7 +44,7 @@ function getPortraitSeed(name) {
|
||||
}
|
||||
|
||||
function getScoreColor(score) {
|
||||
if (score > 0) return '#0A39DF';
|
||||
if (score > 0) return '#0838E0';
|
||||
return '#EF4444';
|
||||
}
|
||||
|
||||
@@ -72,6 +76,66 @@ export default function CandidateCard({ application, jobTitle, rank, onAction, o
|
||||
const canTakeAction = status !== 'hired' && status !== 'rejected';
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
const [internalMessageOpen, setInternalMessageOpen] = useState(false);
|
||||
const [internalScheduleOpen, setInternalScheduleOpen] = useState(false);
|
||||
|
||||
const handleMessage = (e) => {
|
||||
e?.stopPropagation();
|
||||
if (onMessage) {
|
||||
onMessage(application);
|
||||
} else {
|
||||
setInternalMessageOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCall = (e) => {
|
||||
e?.stopPropagation();
|
||||
if (onCall) {
|
||||
onCall(application);
|
||||
} else if (application.phone) {
|
||||
window.location.href = `tel:${application.phone}`;
|
||||
} else {
|
||||
setInternalMessageOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSchedule = (e) => {
|
||||
e?.stopPropagation();
|
||||
if (onSchedule) {
|
||||
onSchedule(application);
|
||||
} else {
|
||||
setInternalScheduleOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDecline = async (e) => {
|
||||
e?.stopPropagation();
|
||||
if (onDecline) {
|
||||
onDecline(application);
|
||||
} else {
|
||||
try {
|
||||
await base44.entities.JobApplication.update(application.id, { status: 'rejected' });
|
||||
toast.success(`${application.applicant_name} declined`);
|
||||
} catch (err) {
|
||||
toast.error('Failed to decline candidate');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (e) => {
|
||||
e?.stopPropagation();
|
||||
if (onDelete) {
|
||||
onDelete(application);
|
||||
} else {
|
||||
try {
|
||||
await base44.entities.JobApplication.delete(application.id);
|
||||
toast.success(`${application.applicant_name} deleted`);
|
||||
} catch (err) {
|
||||
toast.error('Failed to delete candidate');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn('border rounded-xl p-4 transition-all hover:shadow-sm', style.card)}>
|
||||
<div
|
||||
@@ -80,7 +144,7 @@ export default function CandidateCard({ application, jobTitle, rank, onAction, o
|
||||
>
|
||||
{/* Rank */}
|
||||
<div className="flex flex-col items-center justify-center gap-0.5 shrink-0 w-8">
|
||||
<span className="text-[18px] font-bold text-[#0A39DF]">{rank}</span>
|
||||
<span className="text-[18px] font-bold text-[#0838E0]">{rank}</span>
|
||||
</div>
|
||||
|
||||
{/* Avatar — 88-110px target size */}
|
||||
@@ -112,7 +176,7 @@ export default function CandidateCard({ application, jobTitle, rank, onAction, o
|
||||
</div>
|
||||
<div className="flex items-center gap-3 mt-1 text-[11px] text-[#6B7280] flex-wrap">
|
||||
{position && (
|
||||
<span className="inline-flex items-center gap-1 font-medium text-[#0A39DF]">
|
||||
<span className="inline-flex items-center gap-1 font-medium text-[#0838E0]">
|
||||
<Briefcase className="w-3 h-3" />
|
||||
{position}
|
||||
</span>
|
||||
@@ -129,7 +193,7 @@ export default function CandidateCard({ application, jobTitle, rank, onAction, o
|
||||
</div>
|
||||
{application.ai_match_label && (
|
||||
<div className="mt-1.5">
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-[#0A39DF] text-white text-[10px] font-bold">
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-[#0838E0] text-white text-[10px] font-bold">
|
||||
+ {application.ai_match_label}
|
||||
</span>
|
||||
</div>
|
||||
@@ -144,7 +208,7 @@ export default function CandidateCard({ application, jobTitle, rank, onAction, o
|
||||
{onAction && canTakeAction && (
|
||||
<button
|
||||
onClick={() => onAction(application)}
|
||||
className="px-3 py-1.5 rounded-lg bg-[#0A39DF] hover:bg-[#082EB4] text-white text-[12px] font-medium transition-colors whitespace-nowrap"
|
||||
className="px-3 py-1.5 rounded-lg bg-[#0838E0] hover:bg-[#062BAF] text-white text-[12px] font-medium transition-colors whitespace-nowrap"
|
||||
>
|
||||
{status === 'interview' ? 'AI Interview' : 'Interview'}
|
||||
</button>
|
||||
@@ -156,48 +220,50 @@ export default function CandidateCard({ application, jobTitle, rank, onAction, o
|
||||
<ChevronDown className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-48">
|
||||
{onMessage && (
|
||||
<DropdownMenuItem onClick={() => onMessage(application)} className="cursor-pointer py-2">
|
||||
<MessageSquare className="w-4 h-4 mr-2.5 text-[#0A39DF]" />
|
||||
<span className="text-[#374151]">Message</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{onCall && (
|
||||
<DropdownMenuItem onClick={() => onCall(application)} className="cursor-pointer py-2">
|
||||
<Phone className="w-4 h-4 mr-2.5 text-[#059669]" />
|
||||
<span className="text-[#059669]">Call</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{onSchedule && (
|
||||
<DropdownMenuItem onClick={() => onSchedule(application)} className="cursor-pointer py-2">
|
||||
<Calendar className="w-4 h-4 mr-2.5 text-[#0A39DF]" />
|
||||
<span className="text-[#0A39DF]">Set up interview</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuContent align="end" className="w-52 p-1.5 bg-white border border-[#E5E7EB] shadow-xl rounded-xl">
|
||||
<DropdownMenuItem onClick={handleMessage} className="cursor-pointer py-2 px-3 focus:bg-[#F3F4F6] rounded-lg">
|
||||
<MessageSquare className="w-4 h-4 mr-2.5 text-[#0838E0]" />
|
||||
<span className="text-[#374151] text-[13px] font-medium">Message</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={handleCall} className="cursor-pointer py-2 px-3 focus:bg-[#F3F4F6] rounded-lg">
|
||||
<Phone className="w-4 h-4 mr-2.5 text-[#059669]" />
|
||||
<span className="text-[#059669] text-[13px] font-medium">Call</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={handleSchedule} className="cursor-pointer py-2 px-3 focus:bg-[#F3F4F6] rounded-lg">
|
||||
<Calendar className="w-4 h-4 mr-2.5 text-[#0838E0]" />
|
||||
<span className="text-[#0838E0] text-[13px] font-medium">Set up interview</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{onHire && (
|
||||
<DropdownMenuItem onClick={() => onHire(application)} className="cursor-pointer py-2">
|
||||
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onHire(application); }} className="cursor-pointer py-2 px-3 focus:bg-[#F3F4F6] rounded-lg">
|
||||
<UserCheck className="w-4 h-4 mr-2.5 text-[#059669]" />
|
||||
<span className="text-[#059669]">Hire candidate</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{onDecline && (
|
||||
<DropdownMenuItem onClick={() => onDecline(application)} className="cursor-pointer py-2">
|
||||
<X className="w-4 h-4 mr-2.5 text-[#DC2626]" />
|
||||
<span className="text-[#DC2626]">Decline candidate</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{onDelete && (
|
||||
<DropdownMenuItem onClick={() => onDelete(application)} className="cursor-pointer py-2">
|
||||
<Trash2 className="w-4 h-4 mr-2.5 text-[#DC2626]" />
|
||||
<span className="text-[#DC2626]">Delete candidate</span>
|
||||
<span className="text-[#059669] text-[13px] font-medium">Hire candidate</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
<DropdownMenuItem onClick={handleDecline} className="cursor-pointer py-2 px-3 focus:bg-[#F3F4F6] rounded-lg">
|
||||
<X className="w-4 h-4 mr-2.5 text-[#DC2626]" />
|
||||
<span className="text-[#DC2626] text-[13px] font-medium">Decline candidate</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={handleDelete} className="cursor-pointer py-2 px-3 focus:bg-[#F3F4F6] rounded-lg">
|
||||
<Trash2 className="w-4 h-4 mr-2.5 text-[#DC2626]" />
|
||||
<span className="text-[#DC2626] text-[13px] font-medium">Delete candidate</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
{expanded && <CandidateExpandedDetails application={application} />}
|
||||
|
||||
{internalScheduleOpen && (
|
||||
<ScheduleInterviewModal open={true} onClose={() => setInternalScheduleOpen(false)} application={application} />
|
||||
)}
|
||||
{internalMessageOpen && (
|
||||
<MessageCandidateModal open={true} onClose={() => setInternalMessageOpen(false)} application={application} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user