import { Alert, Badge, Button, Card, Group, Modal, NumberInput, Select, Stack, Table, Text, TextInput, } from "@mantine/core"; import { IconAlertTriangle, IconPlus } from "@tabler/icons-react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { Can } from "@/auth/Can"; import { HR_PERMS } from "@/auth/permissions"; import { apiErrorMessage } from "@/auth/http"; import { PageHeader } from "@/shared/components/PageHeader"; import { BilingualTextInput } from "@/shared/components/BilingualTextInput"; import { EthiopianDateInput } from "@/shared/components/EthiopianDateInput"; import { localized } from "@/shared/lib/localizedName"; import { createOpening, listOpenings, publishOpening } from "./api"; import { OpeningBadge } from "./components/StageBadge"; export function OpeningsPage() { const queryClient = useQueryClient(); const navigate = useNavigate(); const { i18n } = useTranslation(); const [creating, setCreating] = useState(false); const [error, setError] = useState(null); const openings = useQuery({ queryKey: ["job-openings"], queryFn: () => listOpenings({ limit: 50 }), }); const publish = useMutation({ mutationFn: (payload: { id: string; closesOn?: string }) => publishOpening(payload.id, payload.closesOn), onSuccess: () => queryClient.invalidateQueries({ queryKey: ["job-openings"] }), onError: (err) => setError(apiErrorMessage(err)), }); const items = openings.data?.items ?? []; return ( <> } /> {error && ( setError(null)} icon={} > {error} )} Vacancy Status Seats Salary range Closes {items.map((opening) => ( navigate(`/recruitment/${opening.id}`)} > {localized(opening.title, i18n.language) || opening.reference} {opening.reference} {opening.visibility.toLowerCase()} {opening.filled} of {opening.openings} {opening.salaryRangeMin && opening.salaryRangeMax ? `${Number(opening.salaryRangeMin).toLocaleString()} — ${Number( opening.salaryRangeMax, ).toLocaleString()}` : "—"} {opening.closesOn ?? "—"} event.stopPropagation()}> {(opening.status === "DRAFT" || opening.status === "ON_HOLD") && ( )} ))} {!openings.isLoading && items.length === 0 && ( No vacancies yet. )}
setCreating(false)} /> ); } function NewOpeningModal({ opened, onClose, }: { opened: boolean; onClose: () => void; }) { const queryClient = useQueryClient(); const [reference, setReference] = useState(""); const [title, setTitle] = useState({ am: "", en: "" }); const [positionId, setPositionId] = useState(""); const [openings, setOpenings] = useState(1); const [visibility, setVisibility] = useState("INTERNAL"); const [closesOn, setClosesOn] = useState(); const [salaryMin, setSalaryMin] = useState(""); const [salaryMax, setSalaryMax] = useState(""); const create = useMutation({ mutationFn: () => createOpening({ reference, title, positionId: positionId || undefined, openings, visibility, closesOn, salaryRangeMin: salaryMin || undefined, salaryRangeMax: salaryMax || undefined, }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["job-openings"] }); setReference(""); setTitle({ am: "", en: "" }); setPositionId(""); onClose(); }, }); return ( {create.isError && ( }> {apiErrorMessage(create.error)} )} setReference(event.currentTarget.value.toUpperCase())} /> setPositionId(event.currentTarget.value)} /> setOpenings(Number(value) || 1)} />