Skip to content

Commit 2aa2d91

Browse files
authored
Merge pull request #598 from trycompai/main
[comp] Production Deploy
2 parents 051babb + e69bd32 commit 2aa2d91

File tree

22 files changed

+1562
-16
lines changed

22 files changed

+1562
-16
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use client'
2+
3+
import { useState } from 'react';
4+
import { useRouter } from 'next/navigation';
5+
import type { FrameworkEditorPolicyTemplate } from '@prisma/client';
6+
import PageLayout from "@/app/components/PageLayout";
7+
import { DataTable } from "@/app/components/DataTable";
8+
import { columns } from "./components/columns";
9+
import { CreatePolicyDialog } from "./components/CreatePolicyDialog";
10+
11+
interface PoliciesClientPageProps {
12+
initialPolicies: FrameworkEditorPolicyTemplate[];
13+
}
14+
15+
export function PoliciesClientPage({ initialPolicies }: PoliciesClientPageProps) {
16+
const [isCreatePolicyDialogOpen, setIsCreatePolicyDialogOpen] = useState(false);
17+
const router = useRouter();
18+
19+
const handleRowClick = (policy: FrameworkEditorPolicyTemplate) => {
20+
router.push(`/policies/${policy.id}`);
21+
};
22+
23+
return (
24+
<PageLayout
25+
breadcrumbs={[{ label: "Policies", href: "/policies" }]}
26+
>
27+
<DataTable
28+
data={initialPolicies}
29+
columns={columns}
30+
searchQueryParamName="policies-search"
31+
createButtonLabel="Create Policy"
32+
onCreateClick={() => setIsCreatePolicyDialogOpen(true)}
33+
onRowClick={handleRowClick}
34+
/>
35+
<CreatePolicyDialog
36+
isOpen={isCreatePolicyDialogOpen}
37+
onOpenChange={setIsCreatePolicyDialogOpen}
38+
/>
39+
</PageLayout>
40+
);
41+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
import { useRouter } from 'next/navigation';
5+
import { Button } from '@comp/ui/button';
6+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@comp/ui/card";
7+
import { PencilIcon, Trash2 } from 'lucide-react';
8+
import type { FrameworkEditorPolicyTemplate } from '@prisma/client'; // Assuming this is the correct type
9+
10+
// Import the actual dialog components
11+
import { EditPolicyDialog } from './components/EditPolicyDialog';
12+
import { DeletePolicyDialog } from './components/DeletePolicyDialog';
13+
14+
interface PolicyDetailsClientPageProps {
15+
policy: FrameworkEditorPolicyTemplate; // Use the specific Prisma type
16+
}
17+
18+
export function PolicyDetailsClientPage({ policy }: PolicyDetailsClientPageProps) {
19+
const router = useRouter();
20+
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
21+
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
22+
23+
const handlePolicyUpdated = () => {
24+
setIsEditDialogOpen(false);
25+
router.refresh();
26+
};
27+
28+
const handlePolicyDeleted = () => {
29+
setIsDeleteDialogOpen(false);
30+
router.push('/policies'); // Navigate back to policies list
31+
};
32+
33+
return (
34+
<>
35+
<Card className="w-full shadow-none rounded-sm">
36+
<CardHeader className="pb-2">
37+
<div className="flex justify-between items-start">
38+
<div>
39+
<CardTitle className="text-2xl font-bold flex items-center gap-2">
40+
{policy.name}
41+
</CardTitle>
42+
{policy.description && (
43+
<CardDescription className="mt-2 text-base">
44+
{policy.description}
45+
</CardDescription>
46+
)}
47+
</div>
48+
<div className="flex gap-2">
49+
<Button variant="outline" size="sm" onClick={() => setIsEditDialogOpen(true)} className="gap-1 rounded-sm">
50+
<PencilIcon className="h-4 w-4" />
51+
Edit Details
52+
</Button>
53+
<Button variant="destructive" size="sm" onClick={() => setIsDeleteDialogOpen(true)} className="gap-1 rounded-sm">
54+
<Trash2 className="h-4 w-4" />
55+
Delete Policy
56+
</Button>
57+
</div>
58+
</div>
59+
</CardHeader>
60+
<CardContent>
61+
<div className="space-y-2">
62+
<p><strong>Frequency:</strong> {policy.frequency || 'N/A'}</p>
63+
<p><strong>Department:</strong> {policy.department || 'N/A'}</p>
64+
{/* <p><strong>ID:</strong> {policy.id}</p> */}
65+
</div>
66+
</CardContent>
67+
</Card>
68+
69+
{/* Render Edit Dialog */}
70+
{isEditDialogOpen && (
71+
<EditPolicyDialog
72+
policy={policy}
73+
isOpen={isEditDialogOpen}
74+
onClose={() => setIsEditDialogOpen(false)}
75+
onPolicyUpdated={handlePolicyUpdated}
76+
/>
77+
)}
78+
79+
{/* Render Delete Dialog */}
80+
{isDeleteDialogOpen && (
81+
<DeletePolicyDialog
82+
policyId={policy.id}
83+
policyName={policy.name}
84+
isOpen={isDeleteDialogOpen}
85+
onClose={() => setIsDeleteDialogOpen(false)}
86+
onPolicyDeleted={handlePolicyDeleted}
87+
/>
88+
)}
89+
</>
90+
);
91+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use client';
2+
3+
import { PolicyEditor } from "@/app/components/editor/PolicyEditor"; // Use PolicyEditor from framework-editor
4+
import type { JSONContent } from "@tiptap/react"; // Or from 'novel'
5+
import { updatePolicyContent } from "./actions"; // Local server action
6+
import { toast } from "sonner";
7+
import { Card, CardContent, CardHeader, CardTitle } from "@comp/ui/card";
8+
9+
interface PolicyEditorClientProps {
10+
policyId: string;
11+
policyName: string; // For display purposes
12+
initialContent: JSONContent | JSONContent[] | null | undefined; // From DB
13+
}
14+
15+
export function PolicyEditorClient({
16+
policyId,
17+
policyName,
18+
initialContent,
19+
}: PolicyEditorClientProps) {
20+
21+
const handleSavePolicy = async (contentToSave: JSONContent): Promise<void> => {
22+
if (!policyId) return;
23+
24+
// Ensure the content is strictly JSON-serializable before sending to server action
25+
const serializableContent = JSON.parse(JSON.stringify(contentToSave));
26+
27+
try {
28+
// Pass the cleaned-up object to the server action
29+
const result = await updatePolicyContent({ policyId, content: serializableContent });
30+
if (result.success) {
31+
toast.success("Policy content saved!");
32+
} else {
33+
toast.error(result.message || "Failed to save policy content.");
34+
}
35+
} catch (error) {
36+
console.error("Error saving policy content:", error);
37+
toast.error("An unexpected error occurred while saving.");
38+
// Re-throw if AdvancedEditor needs to handle it for save status
39+
throw error;
40+
}
41+
};
42+
43+
return (
44+
<PolicyEditor
45+
initialDbContent={initialContent}
46+
onSave={handleSavePolicy}
47+
/>
48+
);
49+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use server';
2+
3+
import { db } from '@comp/db';
4+
import type { JSONContent } from '@tiptap/react'; // Or from 'novel' if it exports it
5+
import { revalidatePath } from 'next/cache';
6+
7+
interface UpdatePolicyContentArgs {
8+
policyId: string;
9+
content: JSONContent | JSONContent[]; // Expecting the full Tiptap object now
10+
}
11+
12+
export async function updatePolicyContent({ policyId, content }: UpdatePolicyContentArgs): Promise<{ success: boolean; message?: string }> {
13+
if (!policyId) {
14+
return { success: false, message: 'Policy ID is required.' };
15+
}
16+
17+
// Ensure we received the full Tiptap document object and extract the content array
18+
let contentToSave: JSONContent[];
19+
if (
20+
typeof content === 'object' &&
21+
content !== null &&
22+
!Array.isArray(content) && // Ensure it's not already just an array
23+
content.type === 'doc' &&
24+
Array.isArray(content.content)
25+
) {
26+
contentToSave = content.content;
27+
} else {
28+
// Handle cases where content is not the expected format
29+
console.error("Invalid content format received by updatePolicyContent:", content);
30+
return { success: false, message: 'Invalid content format received.' };
31+
}
32+
33+
try {
34+
await db.frameworkEditorPolicyTemplate.update({
35+
where: { id: policyId },
36+
data: {
37+
content: contentToSave, // Save only the extracted array
38+
},
39+
});
40+
41+
revalidatePath(`/policies/${policyId}`); // Revalidate the detail page
42+
return { success: true };
43+
44+
} catch (error) {
45+
console.error("Error updating policy content:", error);
46+
return { success: false, message: 'Failed to update policy content.' };
47+
}
48+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
'use client';
2+
3+
import { useState, useTransition } from 'react';
4+
import { toast } from 'sonner';
5+
import { deletePolicyTemplate } from '../../actions'; // Path to server actions
6+
import { Button } from '@comp/ui/button';
7+
import {
8+
AlertDialog,
9+
AlertDialogAction,
10+
AlertDialogCancel,
11+
AlertDialogContent,
12+
AlertDialogDescription,
13+
AlertDialogFooter,
14+
AlertDialogHeader,
15+
AlertDialogTitle,
16+
} from '@comp/ui/alert-dialog';
17+
18+
interface DeletePolicyDialogProps {
19+
policyId: string;
20+
policyName: string;
21+
isOpen: boolean;
22+
onClose: () => void;
23+
onPolicyDeleted: () => void; // Callback after successful deletion
24+
}
25+
26+
export function DeletePolicyDialog({ policyId, policyName, isOpen, onClose, onPolicyDeleted }: DeletePolicyDialogProps) {
27+
const [isPending, startTransition] = useTransition();
28+
29+
const handleDelete = () => {
30+
startTransition(async () => {
31+
try {
32+
const result = await deletePolicyTemplate(policyId);
33+
if (result.success) {
34+
toast.success(result.message || 'Policy deleted successfully!');
35+
onPolicyDeleted(); // Trigger navigation or refresh
36+
onClose(); // Close the dialog
37+
} else {
38+
toast.error(result.message || 'Failed to delete policy.');
39+
}
40+
} catch (error) {
41+
toast.error('An unexpected error occurred.');
42+
console.error("Delete error:", error);
43+
}
44+
});
45+
};
46+
47+
return (
48+
<AlertDialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
49+
<AlertDialogContent className="rounded-sm">
50+
<AlertDialogHeader>
51+
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
52+
<AlertDialogDescription>
53+
This action cannot be undone. This will permanently delete the policy template
54+
<span className="font-semibold">{policyName}</span>.
55+
</AlertDialogDescription>
56+
</AlertDialogHeader>
57+
<AlertDialogFooter>
58+
<AlertDialogCancel onClick={onClose} className="rounded-sm">Cancel</AlertDialogCancel>
59+
<AlertDialogAction
60+
onClick={handleDelete}
61+
disabled={isPending}
62+
className="rounded-sm bg-destructive text-destructive-foreground hover:bg-destructive/90"
63+
>
64+
{isPending ? 'Deleting...' : 'Delete Policy'}
65+
</AlertDialogAction>
66+
</AlertDialogFooter>
67+
</AlertDialogContent>
68+
</AlertDialog>
69+
);
70+
}

0 commit comments

Comments
 (0)