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
@@ -1,22 +1,6 @@
'use client';

import { downloadAllEvidenceZip } from '@/lib/evidence-download';
import {
Button,
Popover,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
Switch,
} from '@trycompai/design-system';
import { ArrowDown } from '@trycompai/design-system/icons';
import { Section, Stack } from '@trycompai/design-system';
import { Download } from 'lucide-react';
import Image from 'next/image';
import { useParams } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'sonner';

interface AuditorViewProps {
initialContent: Record<string, string>;
Expand All @@ -35,84 +19,34 @@ export function AuditorView({
cSuite,
reportSignatory,
}: AuditorViewProps) {
const params = useParams();
const orgId = params.orgId as string;
const [isDownloading, setIsDownloading] = useState(false);
const [includeJson, setIncludeJson] = useState(false);
const [isPopoverOpen, setIsPopoverOpen] = useState(false);

const handleDownloadAllEvidence = async () => {
setIsDownloading(true);
try {
await downloadAllEvidenceZip({
organizationName,
includeJson,
});
toast.success('Evidence package downloaded successfully');
setIsPopoverOpen(false);
} catch (err) {
toast.error('Failed to download evidence. Please try again.');
console.error('Evidence download error:', err);
} finally {
setIsDownloading(false);
}
};

return (
<div className="flex flex-col gap-10">
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Stack gap="xl">
<Section title="Company Information">
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
{logoUrl && (
<a
href={logoUrl}
download={`${organizationName.replace(/[^a-zA-Z0-9]/g, '_')}_logo`}
className="group relative h-14 w-14 shrink-0 overflow-hidden rounded-lg border bg-background transition-all hover:shadow-md"
title="Download logo"
>
<Image src={logoUrl} alt={`${organizationName} logo`} fill className="object-contain" />
<div className="absolute inset-0 flex items-center justify-center bg-black/50 opacity-0 transition-opacity group-hover:opacity-100">
<Download className="h-4 w-4 text-white" />
</div>
</a>
<InfoCell
label="Logo"
className="lg:border-r lg:border-border lg:pr-6"
value={
<a
href={logoUrl}
download={`${organizationName.replace(/[^a-zA-Z0-9]/g, '_')}_logo`}
className="group relative block h-14 w-14 overflow-hidden rounded-lg border bg-background transition-all hover:shadow-md"
title="Download logo"
>
<Image
src={logoUrl}
alt={`${organizationName} logo`}
fill
className="object-contain"
/>
<div className="absolute inset-0 flex items-center justify-center bg-black/50 opacity-0 transition-opacity group-hover:opacity-100">
<Download className="h-4 w-4 text-white" />
</div>
</a>
}
/>
)}
<div>
<h1 className="text-foreground text-xl font-semibold tracking-tight">
{organizationName}
</h1>
<p className="text-muted-foreground text-sm">Company Overview</p>
</div>
</div>

{/* Download All Evidence Button */}
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
<PopoverTrigger style={{ cursor: 'pointer' }}>
<Button variant="outline">Export All Evidence</Button>
</PopoverTrigger>
<PopoverContent align="end" side="bottom" sideOffset={8}>
<PopoverHeader>
<PopoverTitle>Export Options</PopoverTitle>
<PopoverDescription>Download all task evidence as ZIP</PopoverDescription>
</PopoverHeader>
<div className="flex items-center justify-between gap-3 py-1">
<span className="text-sm">Include raw JSON files</span>
<Switch checked={includeJson} onCheckedChange={(checked) => setIncludeJson(checked)} />
</div>
<Button
iconLeft={<ArrowDown />}
onClick={handleDownloadAllEvidence}
disabled={isDownloading}
width="full"
>
{isDownloading ? 'Preparing…' : 'Export'}
</Button>
</PopoverContent>
</Popover>
</div>

{/* Company Information */}
<Section title="Company Information">
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
<InfoCell
label="Employees"
value={employeeCount || '—'}
Expand All @@ -126,13 +60,9 @@ export function AuditorView({
<div>
<div className="flex items-baseline gap-2">
<span className="font-medium">{reportSignatory.fullName}</span>
<span className="text-muted-foreground text-xs">
{reportSignatory.jobTitle}
</span>
</div>
<div className="text-muted-foreground text-xs mt-0.5">
{reportSignatory.email}
<span className="text-muted-foreground text-xs">{reportSignatory.jobTitle}</span>
</div>
<div className="text-muted-foreground text-xs mt-0.5">{reportSignatory.email}</div>
</div>
) : (
'—'
Expand All @@ -141,7 +71,6 @@ export function AuditorView({
/>
<InfoCell
label="Executive Team"
className="sm:col-span-2 lg:col-span-1"
value={
cSuite.length > 0 ? (
<div className="space-y-1">
Expand All @@ -160,9 +89,8 @@ export function AuditorView({
</div>
</Section>

{/* Business Overview */}
<Section title="Business Overview">
<div className="space-y-6">
<Stack gap="lg">
<ContentRow
title="Company Background & Overview of Operations"
content={initialContent['Company Background & Overview of Operations']}
Expand All @@ -172,15 +100,13 @@ export function AuditorView({
content={initialContent['Types of Services Provided']}
/>
<ContentRow title="Mission & Vision" content={initialContent['Mission & Vision']} />
</div>
</Stack>
</Section>

{/* System Architecture */}
<Section title="System Architecture">
<ContentRow title="System Description" content={initialContent['System Description']} />
</Section>

{/* Third Party Dependencies */}
<Section title="Third Party Dependencies">
<div className="grid gap-6 lg:grid-cols-2">
<ContentRow title="Critical Vendors" content={initialContent['Critical Vendors']} />
Expand All @@ -190,20 +116,7 @@ export function AuditorView({
/>
</div>
</Section>
</div>
);
}

function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
<div className="space-y-4">
<div className="flex items-center gap-3 border-b border-border pb-2">
<h2 className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
{title}
</h2>
</div>
{children}
</div>
</Stack>
);
}

Expand All @@ -217,7 +130,7 @@ function InfoCell({
className?: string;
}) {
return (
<div className={className || ''}>
<div className={className}>
<div className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground mb-1.5">
{label}
</div>
Expand All @@ -233,9 +146,7 @@ function ContentRow({ title, content }: { title: string; content?: string }) {
<div className="space-y-1.5">
<h3 className="text-sm font-medium text-foreground">{title}</h3>
{hasContent ? (
<p className="text-sm leading-relaxed text-muted-foreground whitespace-pre-wrap">
{content}
</p>
<p className="text-sm leading-relaxed text-muted-foreground whitespace-pre-wrap">{content}</p>
) : (
<p className="text-xs text-muted-foreground/50">Not yet available</p>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use client';

import { downloadAllEvidenceZip } from '@/lib/evidence-download';
import {
Button,
Popover,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
Switch,
} from '@trycompai/design-system';
import { ArrowDown } from '@trycompai/design-system/icons';
import { useState } from 'react';
import { toast } from 'sonner';

interface ExportEvidenceButtonProps {
organizationName: string;
}

export function ExportEvidenceButton({ organizationName }: ExportEvidenceButtonProps) {
const [isDownloading, setIsDownloading] = useState(false);
const [includeJson, setIncludeJson] = useState(false);
const [isOpen, setIsOpen] = useState(false);

const handleDownload = async () => {
setIsDownloading(true);
try {
await downloadAllEvidenceZip({ organizationName, includeJson });
toast.success('Evidence package downloaded successfully');
setIsOpen(false);
} catch (err) {
toast.error('Failed to download evidence. Please try again.');
console.error('Evidence download error:', err);
} finally {
setIsDownloading(false);
}
};

return (
<Popover open={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger render={<Button variant="outline">Export All Evidence</Button>} />
<PopoverContent align="end" side="bottom" sideOffset={8}>
<PopoverHeader>
<PopoverTitle>Export Options</PopoverTitle>
<PopoverDescription>Download all task evidence as ZIP</PopoverDescription>
</PopoverHeader>
<div className="flex items-center justify-between gap-3 py-1">
<span className="text-sm">Include raw JSON files</span>
<Switch checked={includeJson} onCheckedChange={setIncludeJson} />
</div>
<Button
iconLeft={<ArrowDown />}
onClick={handleDownload}
disabled={isDownloading}
width="full"
>
{isDownloading ? 'Preparing…' : 'Export'}
</Button>
</PopoverContent>
</Popover>
);
}
4 changes: 0 additions & 4 deletions apps/app/src/app/(app)/[orgId]/auditor/(overview)/layout.tsx

This file was deleted.

28 changes: 13 additions & 15 deletions apps/app/src/app/(app)/[orgId]/auditor/(overview)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import PageWithBreadcrumb from '@/components/pages/PageWithBreadcrumb';
import { serverApi } from '@/lib/api-server';
import { parseRolesString } from '@/lib/permissions';
import { PageHeader, PageLayout } from '@trycompai/design-system';
import { Role } from '@db';
import type { Metadata } from 'next';
import { notFound, redirect } from 'next/navigation';
import { AuditorView } from './components/AuditorView';
import { ExportEvidenceButton } from './components/ExportEvidenceButton';

interface PeopleMember {
userId: string;
Expand Down Expand Up @@ -53,7 +54,7 @@ export default async function AuditorPage({
}: {
params: Promise<{ orgId: string }>;
}) {
const { orgId: organizationId } = await params;
await params;

const [membersRes, orgRes, contextRes] = await Promise.all([
serverApi.get<PeopleApiResponse>('/v1/people'),
Expand Down Expand Up @@ -121,25 +122,22 @@ export default async function AuditorPage({
}

return (
<PageWithBreadcrumb
breadcrumbs={[
{
label: 'Auditor View',
href: `/${organizationId}/auditor`,
current: true,
},
]}
<PageLayout
header={
<PageHeader
title={organizationName}
actions={<ExportEvidenceButton organizationName={organizationName} />}
/>
}
>
<AuditorView
initialContent={initialContent}
organizationName={organizationName}
logoUrl={logoUrl}
employeeCount={
initialContent['How many employees do you have?'] || null
}
organizationName={organizationName}
employeeCount={initialContent['How many employees do you have?'] || null}
cSuite={cSuiteData}
reportSignatory={signatoryData}
/>
</PageWithBreadcrumb>
</PageLayout>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,7 @@ function findingHref(finding: FindingWithTask, organizationId: string): string {
return `/${organizationId}/documents/${finding.evidenceSubmission.formType}?tab=findings`;
}
if (finding.scope) {
const peopleTab =
finding.scope === FindingScope.people
? 'people'
: finding.scope === FindingScope.people_tasks
? 'tasks'
: finding.scope === FindingScope.people_devices
? 'devices'
: finding.scope === FindingScope.people_chart
? 'chart'
: 'people';
return `/${organizationId}/people?tab=${peopleTab}`;
return `/${organizationId}/people?tab=findings`;
}
return `/${organizationId}/overview`;
}
Expand Down
Loading
Loading