ghostwriter panel

This commit is contained in:
DaKheera47 2026-02-15 18:52:53 +00:00
parent f9abd6ff74
commit 5a5cbb40ac
2 changed files with 209 additions and 89 deletions

View File

@ -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<string | null>(null);
const [editingEvent, setEditingEvent] = React.useState<StageEvent | null>(
null,
@ -271,90 +281,119 @@ export const JobPage: React.FC = () => {
</CardContent>
</Card>
<div className="space-y-4">
<Card className="border-border/50">
<CardHeader>
<div className="flex items-center justify-between gap-3">
<CardTitle className="flex items-center gap-2 text-base">
<CalendarClock className="h-4 w-4" />
Application details
</CardTitle>
<GhostwriterDrawer job={job} />
</div>
</CardHeader>
<CardContent className="space-y-4">
<div>
<div className="text-[10px] uppercase tracking-wide text-muted-foreground">
Current Stage
</div>
<div className="mt-1 text-sm font-medium">
{currentStage
? STAGE_LABELS[currentStage as ApplicationStage] ||
currentStage
: job?.status}
</div>
</div>
<div>
<div className="text-[10px] uppercase tracking-wide text-muted-foreground">
Outcome
</div>
<div className="mt-1 text-sm font-medium">
{job?.outcome ? job.outcome.replace(/_/g, " ") : "Open"}
</div>
</div>
{job?.closedAt && (
<div>
<div className="text-[10px] uppercase tracking-wide text-muted-foreground">
Closed On
</div>
<div className="mt-1 text-sm font-medium">
{formatTimestamp(job.closedAt)}
</div>
</div>
)}
</CardContent>
</Card>
{tasks.length > 0 && (
<Sheet open={isGhostwriterOpen} onOpenChange={setIsGhostwriterOpen}>
<div className="space-y-4">
<Card className="border-border/50">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-base">
<CalendarClock className="h-4 w-4" />
Upcoming tasks
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
{tasks.map((task) => (
<div
key={task.id}
className="flex items-start justify-between gap-4"
<div className="flex items-center justify-between gap-3">
<CardTitle className="flex items-center gap-2 text-base">
<CalendarClock className="h-4 w-4" />
Application details
</CardTitle>
<SheetTrigger asChild>
<Button
size="sm"
variant="outline"
className="h-8 gap-1.5 text-xs"
disabled={!job}
>
<div className="space-y-1">
<div className="text-sm font-medium text-foreground/90">
{task.title}
</div>
{task.notes && (
<div className="text-xs text-muted-foreground">
{task.notes}
</div>
)}
</div>
<Badge
variant="outline"
className="text-[10px] uppercase tracking-wide"
>
{formatTimestamp(task.dueDate)}
</Badge>
</div>
))}
<PanelRightOpen className="h-3.5 w-3.5" />
Ghostwriter
</Button>
</SheetTrigger>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div>
<div className="text-[10px] uppercase tracking-wide text-muted-foreground">
Current Stage
</div>
<div className="mt-1 text-sm font-medium">
{currentStage
? STAGE_LABELS[currentStage as ApplicationStage] ||
currentStage
: job?.status}
</div>
</div>
<div>
<div className="text-[10px] uppercase tracking-wide text-muted-foreground">
Outcome
</div>
<div className="mt-1 text-sm font-medium">
{job?.outcome ? job.outcome.replace(/_/g, " ") : "Open"}
</div>
</div>
{job?.closedAt && (
<div>
<div className="text-[10px] uppercase tracking-wide text-muted-foreground">
Closed On
</div>
<div className="mt-1 text-sm font-medium">
{formatTimestamp(job.closedAt)}
</div>
</div>
)}
</CardContent>
</Card>
)}
{job && <GhostwriterPanel job={job} />}
</div>
{tasks.length > 0 && (
<Card className="border-border/50">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-base">
<CalendarClock className="h-4 w-4" />
Upcoming tasks
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
{tasks.map((task) => (
<div
key={task.id}
className="flex items-start justify-between gap-4"
>
<div className="space-y-1">
<div className="text-sm font-medium text-foreground/90">
{task.title}
</div>
{task.notes && (
<div className="text-xs text-muted-foreground">
{task.notes}
</div>
)}
</div>
<Badge
variant="outline"
className="text-[10px] uppercase tracking-wide"
>
{formatTimestamp(task.dueDate)}
</Badge>
</div>
))}
</div>
</CardContent>
</Card>
)}
<SheetContent
side="right"
className="w-full p-0 sm:max-w-none lg:w-[40vw]"
>
<div className="h-full overflow-y-auto p-4">
<SheetHeader>
<SheetTitle>Ghostwriter</SheetTitle>
<SheetDescription>
Chat with context from this job and your writing style.
</SheetDescription>
</SheetHeader>
{job && (
<div className="mt-4">
<GhostwriterPanel job={job} />
</div>
)}
</div>
</SheetContent>
</div>
</Sheet>
</div>
<LogEventModal

View File

@ -7,6 +7,7 @@ import {
FileText,
Loader2,
MoreHorizontal,
PanelRightOpen,
RefreshCcw,
Save,
XCircle,
@ -24,6 +25,14 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
import {
@ -39,6 +48,7 @@ import {
JobHeader,
TailoredSummary,
} from "../../components";
import { GhostwriterPanel } from "../../components/ghostwriter/GhostwriterPanel";
import { JobDetailsEditDrawer } from "../../components/JobDetailsEditDrawer";
import { ReadyPanel } from "../../components/ReadyPanel";
import { TailoringEditor } from "../../components/TailoringEditor";
@ -71,6 +81,7 @@ export const JobDetailPanel: React.FC<JobDetailPanelProps> = ({
const [hasUnsavedTailoring, setHasUnsavedTailoring] = useState(false);
const [processingJobId, setProcessingJobId] = useState<string | null>(null);
const [isEditDetailsOpen, setIsEditDetailsOpen] = useState(false);
const [isGhostwriterOpen, setIsGhostwriterOpen] = useState(false);
const saveTailoringRef = useRef<null | (() => Promise<void>)>(null);
const previousSelectedJobIdRef = useRef<string | null>(null);
@ -310,23 +321,93 @@ export const JobDetailPanel: React.FC<JobDetailPanelProps> = ({
if (activeTab === "discovered") {
return (
<DiscoveredPanel
job={selectedJob}
onJobUpdated={onJobUpdated}
onJobMoved={handleJobMoved}
onTailoringDirtyChange={handleTailoringDirtyChange}
/>
<div className="space-y-3">
<div className="flex justify-end">
<Sheet open={isGhostwriterOpen} onOpenChange={setIsGhostwriterOpen}>
<SheetTrigger asChild>
<Button
size="sm"
variant="outline"
className="h-8 gap-1.5 text-xs"
disabled={!selectedJob}
>
<PanelRightOpen className="h-3.5 w-3.5" />
Ghostwriter
</Button>
</SheetTrigger>
<SheetContent
side="right"
className="w-full p-0 sm:max-w-none lg:w-[40vw]"
>
<div className="h-full overflow-y-auto p-4">
<SheetHeader>
<SheetTitle>Ghostwriter</SheetTitle>
<SheetDescription>
Chat with context from this job and your writing style.
</SheetDescription>
</SheetHeader>
{selectedJob && (
<div className="mt-4">
<GhostwriterPanel job={selectedJob} />
</div>
)}
</div>
</SheetContent>
</Sheet>
</div>
<DiscoveredPanel
job={selectedJob}
onJobUpdated={onJobUpdated}
onJobMoved={handleJobMoved}
onTailoringDirtyChange={handleTailoringDirtyChange}
/>
</div>
);
}
if (activeTab === "ready") {
return (
<ReadyPanel
job={selectedJob}
onJobUpdated={onJobUpdated}
onJobMoved={handleJobMoved}
onTailoringDirtyChange={handleTailoringDirtyChange}
/>
<div className="space-y-3">
<div className="flex justify-end">
<Sheet open={isGhostwriterOpen} onOpenChange={setIsGhostwriterOpen}>
<SheetTrigger asChild>
<Button
size="sm"
variant="outline"
className="h-8 gap-1.5 text-xs"
disabled={!selectedJob}
>
<PanelRightOpen className="h-3.5 w-3.5" />
Ghostwriter
</Button>
</SheetTrigger>
<SheetContent
side="right"
className="w-full p-0 sm:max-w-none lg:w-[40vw]"
>
<div className="h-full overflow-y-auto p-4">
<SheetHeader>
<SheetTitle>Ghostwriter</SheetTitle>
<SheetDescription>
Chat with context from this job and your writing style.
</SheetDescription>
</SheetHeader>
{selectedJob && (
<div className="mt-4">
<GhostwriterPanel job={selectedJob} />
</div>
)}
</div>
</SheetContent>
</Sheet>
</div>
<ReadyPanel
job={selectedJob}
onJobUpdated={onJobUpdated}
onJobMoved={handleJobMoved}
onTailoringDirtyChange={handleTailoringDirtyChange}
/>
</div>
);
}