diff --git a/src/app/components/AppNavbar.tsx b/src/app/components/AppNavbar.tsx index e2cc16d..3598166 100644 --- a/src/app/components/AppNavbar.tsx +++ b/src/app/components/AppNavbar.tsx @@ -55,7 +55,7 @@ const NAV_ITEMS = [ }, { to: appPath("/job-os/companies"), - label: "System", + label: "Companies & System", icon: FolderOpen, matches: (pathname: string) => pathname.startsWith(appPath("/job-os/companies")) || diff --git a/src/app/components/job-os/JobOsLayout.tsx b/src/app/components/job-os/JobOsLayout.tsx index f593d83..34f81a4 100644 --- a/src/app/components/job-os/JobOsLayout.tsx +++ b/src/app/components/job-os/JobOsLayout.tsx @@ -6,6 +6,7 @@ import { FileSpreadsheet, FileText, FolderOpen, + LayoutDashboard, Megaphone, Settings, ShieldCheck, @@ -35,6 +36,7 @@ function getActiveSection(pathname: string): "action" | "pipeline" | "system" { const SECTION_NAV = { action: [ + { to: appPath(), label: "Command Center", icon: LayoutDashboard }, { to: appPath("/job-os/sources"), label: "Sources", icon: Compass }, ], pipeline: [ @@ -43,7 +45,7 @@ const SECTION_NAV = { { to: appPath("/job-os/outreach"), label: "Outreach", icon: Megaphone }, ], system: [ - { to: appPath("/job-os/companies"), label: "Target Companies", icon: Building2 }, + { to: appPath("/job-os/companies"), label: "Companies", icon: Building2 }, { to: appPath("/job-os/assets"), label: "Assets", icon: FolderOpen }, { to: appPath("/cv-optimizer"), label: "CV Optimizer", icon: WandSparkles }, { to: appPath("/compliance/afa"), label: "AfA Compliance", icon: ShieldCheck }, diff --git a/src/app/pages/dashboard/DashboardPage.tsx b/src/app/pages/dashboard/DashboardPage.tsx index fef1450..28cd595 100644 --- a/src/app/pages/dashboard/DashboardPage.tsx +++ b/src/app/pages/dashboard/DashboardPage.tsx @@ -1,9 +1,9 @@ import { useMemo, useState } from "react"; -import { useNavigate } from "react-router"; -import { ChevronDown, ChevronUp, Layers3 } from "lucide-react"; +import { NavLink, useNavigate } from "react-router"; +import { ChevronDown, ChevronUp, Compass, LayoutDashboard, Layers3 } from "lucide-react"; import { useApp } from "../../context"; import { useJobOs } from "../../hooks/useJobOs"; -import { UnifiedAddModal } from "../../components/job-os/UnifiedAddModal"; + import { useJobOsSyncSnapshot } from "../../services/jobOsSync"; import { getNextActions, @@ -20,7 +20,7 @@ import { TodayPanel } from "../../components/dashboard/TodayPanel"; import { HotOpportunities } from "../../components/dashboard/HotOpportunities"; import { PipelineStats } from "../../components/dashboard/PipelineStats"; import { ProbabilityPanel } from "../../components/dashboard/ProbabilityPanel"; -import { QuickActions } from "../../components/dashboard/QuickActions"; + import { FirstRunScreen } from "../../components/dashboard/FirstRunScreen"; import { AppPageShell } from "../../components/layout/AppPageShell"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../../components/ui/collapsible"; @@ -32,7 +32,6 @@ export function DashboardPage() { const navigate = useNavigate(); const jobOsSync = useJobOsSyncSnapshot(); const [supportOpen, setSupportOpen] = useState(false); - const [addModalOpen, setAddModalOpen] = useState(false); const { companies, roles, @@ -116,6 +115,32 @@ export function DashboardPage() { showSync settingsContent={settingsContent} /> +
+
+ +
+
{holdDashboard ? ( @@ -156,7 +181,6 @@ export function DashboardPage() {
- setAddModalOpen(true)} /> @@ -197,14 +221,6 @@ export function DashboardPage() {
- setAddModalOpen(false)} - companies={companies} - addCompany={addCompany} - addRole={addRole} - addApplication={addApplication} - /> ); } diff --git a/src/app/pages/job-os/JobOsCompaniesPage.tsx b/src/app/pages/job-os/JobOsCompaniesPage.tsx index de96e0f..ebbd7d9 100644 --- a/src/app/pages/job-os/JobOsCompaniesPage.tsx +++ b/src/app/pages/job-os/JobOsCompaniesPage.tsx @@ -549,8 +549,8 @@ export default function JobOsCompaniesPage() { return ( } > diff --git a/src/app/pages/job-os/JobOsRolesPage.tsx b/src/app/pages/job-os/JobOsRolesPage.tsx index cf43b54..662bfff 100644 --- a/src/app/pages/job-os/JobOsRolesPage.tsx +++ b/src/app/pages/job-os/JobOsRolesPage.tsx @@ -1,6 +1,5 @@ import { Link } from "react-router"; import { useEffect, useMemo, useState } from "react"; -import { useUnsavedChanges } from "../../hooks/useUnsavedChanges"; import { toast } from "sonner"; import { AlertDialog, @@ -18,12 +17,11 @@ import { ArrowUp, ArrowUpDown, BriefcaseBusiness, - Check, CheckCircle2, - ChevronDown, - ChevronRight, Pencil, Rocket, + Search, + SlidersHorizontal, Trash2, WandSparkles, X, @@ -87,7 +85,6 @@ export default function JobOsRolesPage() { applications, assets, cvProfiles, - addRole, updateRole, addApplication, removeRole, @@ -96,12 +93,8 @@ export default function JobOsRolesPage() { replaceState, } = useJobOsContext(); - const [addFormOpen, setAddFormOpen] = useState(false); - const [companySearch, setCompanySearch] = useState(""); - const [companyOptionsOpen, setCompanyOptionsOpen] = useState(false); const [editingRoleId, setEditingRoleId] = useState(null); const [editDraft, setEditDraft] = useState | null>(null); - const [formNotice, setFormNotice] = useState(null); const [actionNotice, setActionNotice] = useState<{ tone: "success" | "error"; message: string } | null>(null); const [sortKey, setSortKey] = useState("createdAt"); const [sortDir, setSortDir] = useState<"asc" | "desc">("desc"); @@ -114,45 +107,26 @@ export default function JobOsRolesPage() { fitMin: "all", status: "all", }); + const [unifiedSearch, setUnifiedSearch] = useState(""); + const [showMoreFilters, setShowMoreFilters] = useState(false); const [selectedJdRoleId, setSelectedJdRoleId] = useState(null); - const [draft, setDraft] = useState>({ - companyId: "", - title: "", - url: "", - location: "", - seniority: "", - track: "TPM", - fitScore: 3, - status: "to_apply", - origin: "self_sourced", - jobDescription: "", - jobDescriptionUpdatedAt: undefined, - }); const companiesById = useMemo( () => new Map(companies.map((company) => [company.id, company])), [companies] ); - const selectedDraftCompany = draft.companyId ? companiesById.get(draft.companyId) : null; - const filteredCompanyOptions = useMemo(() => { - const query = companySearch.trim().toLowerCase(); - const matches = query - ? companies.filter((company) => company.name.toLowerCase().includes(query)) - : companies; - - return matches.slice(0, 50); - }, [companies, companySearch]); const filtered = useMemo(() => { - const companyQuery = tableFilters.company.trim().toLowerCase(); - const titleQuery = tableFilters.title.trim().toLowerCase(); + const searchQuery = unifiedSearch.trim().toLowerCase(); const locationQuery = tableFilters.location.trim().toLowerCase(); return roles.filter((role) => { const companyName = companiesById.get(role.companyId)?.name ?? ""; - if (companyQuery && !companyName.toLowerCase().includes(companyQuery)) return false; - if (titleQuery && !role.title.toLowerCase().includes(titleQuery)) return false; + if (searchQuery) { + const haystack = `${companyName} ${role.title}`.toLowerCase(); + if (!haystack.includes(searchQuery)) return false; + } if (locationQuery && !role.location.toLowerCase().includes(locationQuery)) return false; if ( tableFilters.seniority !== "all" && @@ -168,7 +142,7 @@ export default function JobOsRolesPage() { return true; }); - }, [companiesById, roles, tableFilters]); + }, [companiesById, roles, tableFilters, unifiedSearch]); const sortedRoles = useMemo(() => { const statusRank: Record = { @@ -235,7 +209,7 @@ export default function JobOsRolesPage() { useEffect(() => { resetRolesPage(); - }, [resetRolesPage, sortDir, sortKey, tableFilters]); + }, [resetRolesPage, sortDir, sortKey, tableFilters, unifiedSearch]); const applicationRoleIds = useMemo( () => new Set(applications.map((application) => application.roleId).filter(Boolean)), @@ -311,51 +285,6 @@ export default function JobOsRolesPage() { toast.success("Role updated"); } - async function handleAddRole(): Promise { - const missingFields: string[] = []; - if (!draft.companyId) missingFields.push("company"); - if (!draft.title.trim()) missingFields.push("role title"); - - if (missingFields.length > 0) { - setFormNotice(`Add a ${missingFields.join(" and ")} before creating the role.`); - return; - } - - try { - setFormNotice("Saving role..."); - const createdId = await addRole({ - ...draft, - title: draft.title.trim(), - seniority: normalizeSeniority(draft.seniority), - jobDescriptionUpdatedAt: draft.jobDescription?.trim() ? new Date().toISOString() : undefined, - }); - - if (!createdId) { - setFormNotice("Role could not be created."); - return; - } - - setDraft({ - companyId: "", - title: "", - url: "", - location: "", - seniority: "", - track: "TPM", - fitScore: 3, - status: "to_apply", - origin: "self_sourced", - jobDescription: "", - jobDescriptionUpdatedAt: undefined, - }); - setCompanySearch(""); - setFormNotice(""); - toast.success("Role added"); - } catch (error) { - setFormNotice(error instanceof Error ? error.message : "Role could not be created."); - } - } - async function handleAddApplication(role: JobOsRole): Promise { if (applicationRoleIds.has(role.id)) { setActionNotice({ @@ -396,21 +325,6 @@ export default function JobOsRolesPage() { } } - const isAddFormDirty = - addFormOpen && - (draft.companyId !== "" || - draft.title.trim() !== "" || - (draft.url ?? "").trim() !== "" || - (draft.location ?? "").trim() !== "" || - (draft.jobDescription ?? "").trim() !== ""); - const { confirmDiscard: confirmAddFormDiscard } = useUnsavedChanges(isAddFormDirty); - - function handleToggleAddForm() { - if (addFormOpen && !confirmAddFormDiscard()) return; - setAddFormOpen((value) => !value); - setCompanyOptionsOpen(false); - } - return ( } > - - - + {(unifiedSearch || tableFilters.status !== "all" || tableFilters.location || tableFilters.seniority !== "all" || tableFilters.track !== "all" || tableFilters.fitMin !== "all") && ( + - - {addFormOpen ? ( - -
- { - setCompanySearch(event.target.value); - setCompanyOptionsOpen(true); - setDraft((current) => ({ ...current, companyId: "" })); - }} - onFocus={() => { - setCompanySearch(selectedDraftCompany?.name ?? companySearch); - setCompanyOptionsOpen(true); - }} - onBlur={() => { - window.setTimeout(() => setCompanyOptionsOpen(false), 120); - }} - placeholder="Company" - autoComplete="off" - /> - {companyOptionsOpen ? ( -
- {filteredCompanyOptions.length > 0 ? ( - filteredCompanyOptions.map((company) => ( - - )) - ) : ( -
- No company found. -
- )} -
- ) : null} -
- setDraft((current) => ({ ...current, title: event.target.value }))} placeholder="Role title" /> - - setDraft((current) => ({ ...current, url: event.target.value }))} - placeholder={draft.origin === "recruiter" ? "Posting URL (optional)" : "Role URL"} - /> - setDraft((current) => ({ ...current, location: event.target.value }))} - placeholder="Location" - /> - - {LOCATION_SUGGESTIONS.map((option) => ( -