Files
kandev/apps/web/components/integrations/integration-change-request-status-content.tsx
T

174 lines
5.2 KiB
TypeScript

"use client";
import {
IconCircleCheckFilled,
IconCircleXFilled,
IconLoader2,
IconPointFilled,
} from "@tabler/icons-react";
import { DrawerClose } from "@kandev/ui/drawer";
import { Button } from "@kandev/ui/button";
import { t } from "@/lib/i18n";
import {
ChangeRequestCIPopoverFrame,
ChangeRequestChecksSection,
ChangeRequestCommentsRow,
ChangeRequestPopoverFooter,
ChangeRequestPopoverHeader,
ChangeRequestReviewRow,
type ChangeRequestCheckCounts,
} from "./change-request-ci-anatomy";
import type {
IntegrationChangeRequestPipelineState,
IntegrationChangeRequestStatusItem,
} from "./integration-change-request-status-types";
export function PipelineStatusGlyph({ state }: { state: IntegrationChangeRequestPipelineState }) {
if (state === "success") {
return <IconCircleCheckFilled className="h-3.5 w-3.5 text-green-500" aria-hidden="true" />;
}
if (state === "failure") {
return <IconCircleXFilled className="h-3.5 w-3.5 text-red-500" aria-hidden="true" />;
}
if (state === "pending") {
return (
<IconLoader2
className="h-3.5 w-3.5 text-yellow-500 animate-spin [animation-duration:3s]"
aria-hidden="true"
/>
);
}
return <IconPointFilled className="h-3.5 w-3.5 text-muted-foreground" aria-hidden="true" />;
}
function countsFor(item: IntegrationChangeRequestStatusItem): ChangeRequestCheckCounts {
const counts = { passed: 0, pending: 0, failed: 0 };
item.pipelineRows?.forEach((row) => {
if (row.state === "success") counts.passed += 1;
else if (row.state === "pending") counts.pending += 1;
else if (row.state === "failure") counts.failed += 1;
});
return counts;
}
function StatusBody({ item }: { item: IntegrationChangeRequestStatusItem }) {
if (item.error) {
return <div className="px-1 py-2 text-xs text-destructive">{item.error}</div>;
}
return (
<ChangeRequestChecksSection
counts={countsFor(item)}
rows={item.pipelineRows ?? []}
loading={item.loading}
/>
);
}
export function IntegrationChangeRequestStatusContent({
item,
mobile,
contained = true,
}: {
item: IntegrationChangeRequestStatusItem;
mobile: boolean;
contained?: boolean;
}) {
const content = <StatusFrame item={item} mobile={mobile} />;
if (!contained) return content;
return (
<div
data-testid="integration-change-request-status-scroll-body"
className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden overscroll-contain p-2.5"
data-vaul-no-drag={mobile ? "" : undefined}
>
{content}
</div>
);
}
function StatusFrame({
item,
mobile = false,
}: {
item: IntegrationChangeRequestStatusItem;
mobile?: boolean;
}) {
return (
<ChangeRequestCIPopoverFrame>
<ChangeRequestPopoverHeader
number={item.number}
title={item.title}
url={item.url}
onOpenReview={item.onOpenReview}
onUnlink={item.onUnlink}
mobile={mobile}
/>
<StatusBody item={item} />
{item.review || item.unresolvedComments ? (
<div className="flex flex-col gap-0">
{item.review ? <ChangeRequestReviewRow {...item.review} /> : null}
<ChangeRequestCommentsRow count={item.unresolvedComments ?? 0} />
</div>
) : null}
<ChangeRequestPopoverFooter updatedAt={item.updatedAt} />
</ChangeRequestCIPopoverFrame>
);
}
export function IntegrationChangeRequestMultiStatusContent({
items,
mobile,
contained = true,
}: {
items: readonly IntegrationChangeRequestStatusItem[];
mobile: boolean;
contained?: boolean;
}) {
return (
<div
data-testid={contained ? "integration-change-request-status-scroll-body" : undefined}
className={
contained
? "min-h-0 flex-1 overflow-y-auto overflow-x-hidden overscroll-contain p-2.5"
: "min-h-0"
}
data-vaul-no-drag={mobile ? "" : undefined}
>
{items.map((item) => (
<section key={item.id} className="mb-3 border-b pb-3 last:mb-0 last:border-b-0 last:pb-0">
<ChangeRequestCIPopoverFrame>
<ChangeRequestPopoverHeader
number={item.number}
title={item.title}
url={item.url}
onOpenReview={item.onOpenReview}
onUnlink={item.onUnlink}
mobile={mobile}
/>
<StatusBody item={item} />
{item.review || item.unresolvedComments ? (
<div className="flex flex-col gap-0">
{item.review ? <ChangeRequestReviewRow {...item.review} /> : null}
<ChangeRequestCommentsRow count={item.unresolvedComments ?? 0} />
</div>
) : null}
<ChangeRequestPopoverFooter updatedAt={item.updatedAt} />
</ChangeRequestCIPopoverFrame>
{mobile ? (
<DrawerClose asChild>
<Button
type="button"
variant="outline"
className="mt-2 h-11 w-full cursor-pointer"
onClick={item.onOpenReview}
>
{t("integrations:openReviewNumber", { number: item.number })}
</Button>
</DrawerClose>
) : null}
</section>
))}
</div>
);
}