diff --git a/orchestrator/src/client/pages/JobPage.tsx b/orchestrator/src/client/pages/JobPage.tsx index 2131c92..1b38f1a 100644 --- a/orchestrator/src/client/pages/JobPage.tsx +++ b/orchestrator/src/client/pages/JobPage.tsx @@ -12,6 +12,7 @@ import { CalendarClock, ClipboardList, DollarSign, + PanelRightOpen, PlusCircle, } from "lucide-react"; import React from "react"; @@ -20,6 +21,14 @@ import { toast } from "sonner"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet"; import { formatTimestamp } from "@/lib/utils"; import * as api from "../api"; import { ConfirmDelete } from "../components/ConfirmDelete"; @@ -40,6 +49,7 @@ export const JobPage: React.FC = () => { const [isLoading, setIsLoading] = React.useState(true); const [isLogModalOpen, setIsLogModalOpen] = React.useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = React.useState(false); + const [isGhostwriterOpen, setIsGhostwriterOpen] = React.useState(false); const [eventToDelete, setEventToDelete] = React.useState(null); const [editingEvent, setEditingEvent] = React.useState( null, @@ -271,90 +281,119 @@ export const JobPage: React.FC = () => { -
- - -
- - - Application details - - -
-
- -
-
- Current Stage -
-
- {currentStage - ? STAGE_LABELS[currentStage as ApplicationStage] || - currentStage - : job?.status} -
-
-
-
- Outcome -
-
- {job?.outcome ? job.outcome.replace(/_/g, " ") : "Open"} -
-
- {job?.closedAt && ( -
-
- Closed On -
-
- {formatTimestamp(job.closedAt)} -
-
- )} -
-
- - {tasks.length > 0 && ( + +
- - - Upcoming tasks - - - -
- {tasks.map((task) => ( -
+ + + Application details + + +
- ))} + + Ghostwriter + +
+ + +
+
+ Current Stage +
+
+ {currentStage + ? STAGE_LABELS[currentStage as ApplicationStage] || + currentStage + : job?.status} +
+
+
+
+ Outcome +
+
+ {job?.outcome ? job.outcome.replace(/_/g, " ") : "Open"} +
+
+ {job?.closedAt && ( +
+
+ Closed On +
+
+ {formatTimestamp(job.closedAt)} +
+
+ )}
- )} - {job && } -
+ {tasks.length > 0 && ( + + + + + Upcoming tasks + + + +
+ {tasks.map((task) => ( +
+
+
+ {task.title} +
+ {task.notes && ( +
+ {task.notes} +
+ )} +
+ + {formatTimestamp(task.dueDate)} + +
+ ))} +
+
+
+ )} + + +
+ + Ghostwriter + + Chat with context from this job and your writing style. + + + {job && ( +
+ +
+ )} +
+
+
+ = ({ const [hasUnsavedTailoring, setHasUnsavedTailoring] = useState(false); const [processingJobId, setProcessingJobId] = useState(null); const [isEditDetailsOpen, setIsEditDetailsOpen] = useState(false); + const [isGhostwriterOpen, setIsGhostwriterOpen] = useState(false); const saveTailoringRef = useRef Promise)>(null); const previousSelectedJobIdRef = useRef(null); @@ -310,23 +321,93 @@ export const JobDetailPanel: React.FC = ({ if (activeTab === "discovered") { return ( - +
+
+ + + + + +
+ + Ghostwriter + + Chat with context from this job and your writing style. + + + {selectedJob && ( +
+ +
+ )} +
+
+
+
+ +
); } if (activeTab === "ready") { return ( - +
+
+ + + + + +
+ + Ghostwriter + + Chat with context from this job and your writing style. + + + {selectedJob && ( +
+ +
+ )} +
+
+
+
+ +
); }