Skip to content

Commit eb9df88

Browse files
authored
Merge pull request #175 from trycompai/mariano/portal
Overview fully SSR
2 parents 0956dde + 9a9eeab commit eb9df88

19 files changed

+369
-531
lines changed

apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/actions/getFrameworkCategoriesAction.ts

Lines changed: 0 additions & 76 deletions
This file was deleted.

apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/components/FrameworkControls.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,32 @@
11
"use client";
22

3-
import { FrameworkControlsTable } from "./table/FrameworkControlsTable";
4-
import { useOrganizationCategories } from "../hooks/useOrganizationCategories";
53
import { useMemo } from "react";
4+
import type { FrameworkCategories } from "../data/getFrameworkCategories";
5+
import { FrameworkControlsTable } from "./table/FrameworkControlsTable";
66
import type { OrganizationControlType } from "./table/FrameworkControlsTableColumns";
77

8-
interface FrameworkControlsProps {
8+
export type FrameworkControlsProps = {
9+
organizationCategories: FrameworkCategories;
910
frameworkId: string;
10-
}
11-
12-
export function FrameworkControls({ frameworkId }: FrameworkControlsProps) {
13-
const { data: organizationCategories } =
14-
useOrganizationCategories(frameworkId);
11+
};
1512

13+
export function FrameworkControls({
14+
organizationCategories,
15+
frameworkId,
16+
}: FrameworkControlsProps) {
1617
const allControls = useMemo(() => {
1718
if (!organizationCategories) return [];
1819

1920
return organizationCategories.flatMap((category) =>
20-
category.organizationControl.map((control) => ({
21-
code: control.control.code,
22-
description: control.control.description,
23-
name: control.control.name,
24-
status: control.status,
25-
id: control.id,
21+
category.organizationControl.map((orgControl) => ({
22+
code: orgControl.control.code,
23+
description: orgControl.control.description,
24+
name: orgControl.control.name,
25+
status: orgControl.status,
26+
id: orgControl.id,
2627
frameworkId,
2728
category: category.name,
28-
requirements: control.OrganizationControlRequirement,
29+
requirements: orgControl.OrganizationControlRequirement,
2930
})),
3031
);
3132
}, [organizationCategories, frameworkId]) as OrganizationControlType[];

apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/components/FrameworkOverview.tsx

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,87 @@
11
"use client";
22

3+
import type { Framework, OrganizationFramework } from "@bubba/db/types";
34
import { Badge } from "@bubba/ui/badge";
45
import { Card, CardContent, CardHeader, CardTitle } from "@bubba/ui/card";
56
import { Progress } from "@bubba/ui/progress";
67
import { format } from "date-fns";
78
import { CalendarIcon } from "lucide-react";
8-
import { useOrganizationCategories } from "../hooks/useOrganizationCategories";
9-
import { useOrganizationFramework } from "../hooks/useOrganizationFramework";
10-
9+
import type { FrameworkCategories } from "../data/getFrameworkCategories";
1110
interface FrameworkOverviewProps {
12-
frameworkId: string;
11+
organizationCategories: FrameworkCategories;
12+
organizationFramework: OrganizationFramework & { framework: Framework };
1313
}
1414

15-
export function FrameworkOverview({ frameworkId }: FrameworkOverviewProps) {
16-
const { data } = useOrganizationCategories(frameworkId);
17-
const { data: framework } = useOrganizationFramework(frameworkId);
15+
export function FrameworkOverview({
16+
organizationCategories,
17+
organizationFramework,
18+
}: FrameworkOverviewProps) {
19+
const requirements = organizationCategories.flatMap((category) =>
20+
category.organizationControl.flatMap(
21+
(control) => control.OrganizationControlRequirement,
22+
),
23+
);
24+
25+
const totalRequirements = requirements.length;
26+
const completedRequirements = requirements.filter((req) => {
27+
switch (req.type) {
28+
case "policy":
29+
return req.organizationPolicy?.status === "published";
30+
case "file":
31+
return !!req.fileUrl;
32+
case "evidence":
33+
return req.organizationEvidence?.published === true;
34+
default:
35+
return req.published;
36+
}
37+
}).length;
1838

1939
// Calculate compliance metrics
20-
const totalControls = data?.reduce(
21-
(acc, cat) => acc + cat.organizationControl.length,
22-
0,
23-
);
40+
const compliancePercentage =
41+
totalRequirements > 0
42+
? Math.round((completedRequirements / totalRequirements) * 100)
43+
: 0;
2444

25-
const compliantControls = data?.reduce(
26-
(acc, cat) =>
27-
acc +
28-
cat.organizationControl.filter((oc) => oc.status === "compliant").length,
29-
0,
45+
// Count controls
46+
const allControls = organizationCategories.flatMap(
47+
(category) => category.organizationControl,
3048
);
49+
const totalControls = allControls.length;
3150

32-
const compliancePercentage = Math.round(
33-
(compliantControls ?? 0 / (totalControls ?? 0)) * 100,
34-
);
51+
// Calculate compliant controls (all requirements completed)
52+
const compliantControls = allControls.filter((control) => {
53+
const controlRequirements = control.OrganizationControlRequirement;
54+
if (controlRequirements.length === 0) return false;
55+
56+
const completedControlRequirements = controlRequirements.filter((req) => {
57+
switch (req.type) {
58+
case "policy":
59+
return req.organizationPolicy?.status === "published";
60+
case "file":
61+
return !!req.fileUrl;
62+
case "evidence":
63+
return req.organizationEvidence?.published === true;
64+
default:
65+
return req.published;
66+
}
67+
}).length;
68+
69+
return completedControlRequirements === controlRequirements.length;
70+
}).length;
3571

3672
return (
3773
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
3874
<Card>
3975
<CardHeader>
40-
<CardTitle>{framework?.framework.name}</CardTitle>
76+
<CardTitle>{organizationFramework?.framework.name}</CardTitle>
4177
</CardHeader>
4278
<CardContent>
4379
<p className="text-sm text-muted-foreground">
44-
{framework?.framework.description}
80+
{organizationFramework?.framework.description}
4581
</p>
4682
<div className="mt-4">
4783
<Badge variant="outline">
48-
Version {framework?.framework.version}
84+
Version {organizationFramework?.framework.version}
4985
</Badge>
5086
</div>
5187
</CardContent>
@@ -75,17 +111,17 @@ export function FrameworkOverview({ frameworkId }: FrameworkOverviewProps) {
75111
<CalendarIcon className="h-4 w-4 text-muted-foreground" />
76112
<span className="text-sm">
77113
Last assessed:{" "}
78-
{framework?.lastAssessed
79-
? format(framework?.lastAssessed, "MMM d, yyyy")
114+
{organizationFramework?.lastAssessed
115+
? format(organizationFramework?.lastAssessed, "MMM d, yyyy")
80116
: "Never"}
81117
</span>
82118
</div>
83119
<div className="flex items-center gap-2">
84120
<CalendarIcon className="h-4 w-4 text-muted-foreground" />
85121
<span className="text-sm">
86122
Next assessment:{" "}
87-
{framework?.nextAssessment
88-
? format(framework?.nextAssessment, "MMM d, yyyy")
123+
{organizationFramework?.nextAssessment
124+
? format(organizationFramework?.nextAssessment, "MMM d, yyyy")
89125
: "Not scheduled"}
90126
</span>
91127
</div>

apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/components/table/FrameworkControlsTableColumns.tsx

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
TooltipTrigger,
1919
} from "@bubba/ui/tooltip";
2020
import { useParams } from "next/navigation";
21+
import { getControlStatus } from "../../lib/utils";
2122
export type OrganizationControlType = {
2223
code: string;
2324
description: string | null;
@@ -36,30 +37,6 @@ export type OrganizationControlType = {
3637
})[];
3738
};
3839

39-
function getControlStatus(
40-
requirements: OrganizationControlType["requirements"],
41-
): StatusType {
42-
if (!requirements || requirements.length === 0) return "not_started";
43-
44-
const totalRequirements = requirements.length;
45-
const completedRequirements = requirements.filter((req) => {
46-
switch (req.type) {
47-
case "policy":
48-
return req.organizationPolicy?.status === "published";
49-
case "file":
50-
return !!req.fileUrl;
51-
case "evidence":
52-
return req.organizationEvidence?.published === true;
53-
default:
54-
return req.published;
55-
}
56-
}).length;
57-
58-
if (completedRequirements === 0) return "not_started";
59-
if (completedRequirements === totalRequirements) return "completed";
60-
return "in_progress";
61-
}
62-
6340
export function FrameworkControlsTableColumns(): ColumnDef<OrganizationControlType>[] {
6441
const t = useI18n();
6542
const { orgId } = useParams<{ orgId: string }>();
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { db } from "@bubba/db";
2+
3+
export const getFramework = async (
4+
frameworkId: string,
5+
organizationId: string
6+
) => {
7+
const framework = await db.organizationFramework.findUnique({
8+
where: {
9+
organizationId_frameworkId: {
10+
organizationId,
11+
frameworkId,
12+
},
13+
},
14+
include: {
15+
framework: true,
16+
organizationControl: true,
17+
},
18+
});
19+
20+
return framework;
21+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { db } from "@bubba/db";
2+
import type {
3+
OrganizationControl,
4+
OrganizationControlRequirement,
5+
Control,
6+
OrganizationCategory,
7+
OrganizationPolicy,
8+
OrganizationEvidence,
9+
} from "@bubba/db/types";
10+
11+
export type FrameworkCategories = (OrganizationCategory & {
12+
name: string;
13+
organizationControl: (OrganizationControl & {
14+
id: string;
15+
status: any; // ComplianceStatus enum
16+
control: Control;
17+
OrganizationControlRequirement: (OrganizationControlRequirement & {
18+
organizationPolicy: OrganizationPolicy | null;
19+
organizationEvidence: OrganizationEvidence | null;
20+
})[];
21+
})[];
22+
})[];
23+
24+
export const getFrameworkCategories = async (
25+
frameworkId: string,
26+
organizationId: string
27+
) => {
28+
const organizationCategories = await db.organizationCategory.findMany({
29+
where: {
30+
organizationId,
31+
frameworkId,
32+
},
33+
include: {
34+
organizationControl: {
35+
include: {
36+
control: true,
37+
OrganizationControlRequirement: {
38+
include: {
39+
organizationPolicy: true,
40+
organizationEvidence: true,
41+
},
42+
},
43+
},
44+
},
45+
},
46+
});
47+
48+
return organizationCategories;
49+
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { OrganizationControlType } from "../components/table/FrameworkControlsTableColumns";
2+
import type { StatusType } from "@/components/frameworks/framework-status";
3+
4+
export function getControlStatus(
5+
requirements: OrganizationControlType["requirements"]
6+
): StatusType {
7+
if (!requirements || requirements.length === 0) return "not_started";
8+
9+
const totalRequirements = requirements.length;
10+
const completedRequirements = requirements.filter((req) => {
11+
switch (req.type) {
12+
case "policy":
13+
return req.organizationPolicy?.status === "published";
14+
case "file":
15+
return !!req.fileUrl;
16+
case "evidence":
17+
return req.organizationEvidence?.published === true;
18+
default:
19+
return req.published;
20+
}
21+
}).length;
22+
23+
if (completedRequirements === 0) return "not_started";
24+
if (completedRequirements === totalRequirements) return "completed";
25+
return "in_progress";
26+
}

0 commit comments

Comments
 (0)