Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"use server";

import { authActionClient } from "@/actions/safe-action";
import { db } from "@bubba/db";
import type { Prisma } from "@bubba/db";
import { z } from "zod";

// Define the response types for better type safety
export interface EvidenceTasksStats {
totalCount: number;
emptyCount: number;
draftCount: number;
needsReviewCount: number;
upToDateCount: number;
}

export const getEvidenceTasksStats = authActionClient
.schema(z.object({}))
.metadata({
name: "getEvidenceTasksStats",
track: {
event: "get-evidence-tasks-stats",
channel: "server",
},
})
.action(async ({ ctx }) => {
const { user } = ctx;

if (!user.organizationId) {
return {
success: false,
error: "Not authorized - no organization found",
};
}

try {
// Get all evidence tasks for the organization
const evidenceTasks = await db.organizationEvidence.findMany({
where: {
organizationId: user.organizationId,
},
select: {
fileUrls: true,
additionalUrls: true,
published: true,
lastPublishedAt: true,
frequency: true,
},
});

// Calculate statistics
let emptyCount = 0;
let draftCount = 0;
let needsReviewCount = 0;
let upToDateCount = 0;
const totalCount = evidenceTasks.length;
const now = new Date();

for (const task of evidenceTasks) {
// Check if task has files or links
const hasContent =
task.fileUrls.length > 0 || task.additionalUrls.length > 0;

// Check if task is published
const isPublished = task.published;

// Check if task needs review (published and next review date is in the past)
let nextReviewDate = null;

if (task.lastPublishedAt && task.frequency) {
// Calculate next review date based on last published date and frequency
const lastPublished = new Date(task.lastPublishedAt);
nextReviewDate = new Date(lastPublished);

switch (task.frequency) {
case "monthly":
nextReviewDate.setMonth(nextReviewDate.getMonth() + 1);
break;
case "quarterly":
nextReviewDate.setMonth(nextReviewDate.getMonth() + 3);
break;
case "yearly":
nextReviewDate.setFullYear(nextReviewDate.getFullYear() + 1);
break;
}
}

const isPastDue = nextReviewDate && nextReviewDate < now;

if (!hasContent) {
// No files or links
emptyCount++;
} else if (!isPublished) {
// Has content but not published
draftCount++;
} else if (isPastDue) {
// Published but needs review
needsReviewCount++;
} else {
// Published and up to date
upToDateCount++;
}
}

return {
success: true,
data: {
totalCount,
emptyCount,
draftCount,
needsReviewCount,
upToDateCount,
},
};
} catch (error) {
console.error("Error fetching evidence tasks stats:", error);
return {
success: false,
error: "Failed to fetch evidence tasks stats",
};
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"use server";

import { authActionClient } from "@/actions/safe-action";
import { db } from "@bubba/db";
import { MembershipRole } from "@prisma/client";

export const getOrganizationAdmins = authActionClient
.metadata({
name: "getOrganizationAdmins",
track: {
event: "get-organization-admins",
channel: "server",
},
})
.action(async ({ ctx }) => {
const { user } = ctx;

if (!user.organizationId) {
return {
success: false,
error: "Not authorized - no organization found",
};
}

try {
// Find organization members with admin or owner roles
const adminMembers = await db.organizationMember.findMany({
where: {
organizationId: user.organizationId,
role: {
in: [MembershipRole.admin, MembershipRole.owner],
},
},
include: {
user: {
select: {
id: true,
name: true,
email: true,
image: true,
},
},
},
orderBy: {
user: {
name: "asc",
},
},
});

// Transform the data to a simpler format
const admins = adminMembers.map((member) => ({
id: member.userId,
name: member.user.name || "Unknown",
email: member.user.email || "",
image: member.user.image || null,
role: member.role,
}));

return {
success: true,
data: admins,
};
} catch (error) {
console.error("Error fetching organization admins:", error);
return {
success: false,
error: "Failed to fetch organization admins",
};
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"use server";

import { authActionClient } from "@/actions/safe-action";
import { db } from "@bubba/db";
import { MembershipRole } from "@prisma/client";

export const getOrganizationAdmins = authActionClient
.metadata({
name: "getOrganizationAdmins",
track: {
event: "get-organization-admins",
channel: "server",
},
})
.action(async ({ ctx }) => {
const { user } = ctx;

if (!user.organizationId) {
return {
success: false,
error: "Not authorized - no organization found",
};
}

try {
// Find organization members with admin or owner roles
const adminMembers = await db.organizationMember.findMany({
where: {
organizationId: user.organizationId,
role: {
in: [MembershipRole.admin, MembershipRole.owner],
},
},
include: {
user: {
select: {
id: true,
name: true,
email: true,
},
},
},
orderBy: {
user: {
name: "asc",
},
},
});

// Transform the data to a simpler format
const admins = adminMembers.map((member) => ({
id: member.userId,
name: member.user.name || "Unknown",
email: member.user.email || "",
role: member.role,
}));

return {
success: true,
data: admins,
};
} catch (error) {
console.error("Error fetching organization admins:", error);
return {
success: false,
error: "Failed to fetch organization admins",
};
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const getOrganizationEvidenceById = authActionClient
},
include: {
evidence: true,
assignee: true,
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use server";

import { authActionClient } from "@/actions/safe-action";
import { db, Frequency } from "@bubba/db";
import { db, Frequency, Departments } from "@bubba/db";
import type { Prisma, OrganizationEvidence } from "@bubba/db";
import { z } from "zod";

Expand All @@ -26,6 +26,8 @@ export const getOrganizationEvidenceTasks = authActionClient
search: z.string().optional().nullable(),
status: z.enum(["published", "draft"]).optional().nullable(),
frequency: z.nativeEnum(Frequency).optional().nullable(),
department: z.nativeEnum(Departments).optional().nullable(),
assigneeId: z.string().optional().nullable(),
page: z.number().int().positive().optional().default(1),
pageSize: z.number().int().positive().optional().default(10),
})
Expand All @@ -39,9 +41,18 @@ export const getOrganizationEvidenceTasks = authActionClient
})
.action(async ({ ctx, parsedInput }) => {
const { user } = ctx;
const { search, status, frequency, page, pageSize } = parsedInput;
const {
search,
status,
frequency,
department,
assigneeId,
page,
pageSize,
} = parsedInput;

if (!user.organizationId) {
console.error("Not authorized - no organization found");
return {
success: false,
error: "Not authorized - no organization found",
Expand All @@ -57,6 +68,10 @@ export const getOrganizationEvidenceTasks = authActionClient
...(status === "draft" ? { published: false } : {}),
// Frequency filter
...(frequency ? { frequency } : {}),
// Department filter
...(department ? { department } : {}),
// Assignee filter
...(assigneeId ? { assigneeId } : {}),
// Search filter
...(search
? {
Expand Down Expand Up @@ -100,6 +115,14 @@ export const getOrganizationEvidenceTasks = authActionClient
where: whereClause,
include: {
evidence: true,
assignee: {
select: {
id: true,
name: true,
email: true,
image: true,
},
},
},
skip,
take: pageSize,
Expand Down
Loading