ghostwriter panel
This commit is contained in:
parent
f9abd6ff74
commit
5a5cbb40ac
@ -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
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user