import { useMemo } from "react"; import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Plus, RefreshCw, Building2, Check, AlertCircle, Clock } from "lucide-react"; import { SiGithub } from "react-icons/si"; import type { Organization } from "@/lib/db/schema"; import type { FilterParams } from "@/types/filter"; import Fuse from "fuse.js"; import { Skeleton } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; interface OrganizationListProps { organizations: Organization[]; isLoading: boolean; filter: FilterParams; setFilter: (filter: FilterParams) => void; onMirror: ({ orgId }: { orgId: string }) => Promise; loadingOrgIds: Set; onAddOrganization?: () => void; } // Helper function to get status badge variant and icon const getStatusBadge = (status: string | null) => { switch (status) { case "imported": return { variant: "secondary" as const, label: "Not Mirrored", icon: null }; case "mirroring": return { variant: "outline" as const, label: "Mirroring", icon: Clock }; case "mirrored": return { variant: "default" as const, label: "Mirrored", icon: Check }; case "failed": return { variant: "destructive" as const, label: "Failed", icon: AlertCircle }; default: return { variant: "secondary" as const, label: "Unknown", icon: null }; } }; export function OrganizationList({ organizations, isLoading, filter, setFilter, onMirror, loadingOrgIds, onAddOrganization, }: OrganizationListProps) { const hasAnyFilter = Object.values(filter).some( (val) => val?.toString().trim() !== "" ); const filteredOrganizations = useMemo(() => { let result = organizations; if (filter.membershipRole) { result = result.filter((org) => org.membershipRole === filter.membershipRole); } if (filter.status) { result = result.filter((org) => org.status === filter.status); } if (filter.searchTerm) { const fuse = new Fuse(result, { keys: ["name", "type"], threshold: 0.3, }); result = fuse.search(filter.searchTerm).map((res) => res.item); } return result; }, [organizations, filter]); return isLoading ? (
{Array.from({ length: 5 }).map((_, i) => ( ))}
) : filteredOrganizations.length === 0 ? (

No organizations found

{hasAnyFilter ? "Try adjusting your search or filter criteria." : "Add GitHub organizations to mirror their repositories."}

{hasAnyFilter ? ( ) : ( )}
) : (
{filteredOrganizations.map((org, index) => { const isLoading = loadingOrgIds.has(org.id ?? ""); const statusBadge = getStatusBadge(org.status); const StatusIcon = statusBadge.icon; return (
{org.name} {org.membershipRole}
{StatusIcon && } {statusBadge.label}
{org.repositoryCount}{" "} {org.repositoryCount === 1 ? "repository" : "repositories"}
{/* Always render this section to prevent layout shift */}
{isLoading || (org.status === "mirroring" && org.publicRepositoryCount === undefined) ? ( <> ) : ( <> {org.publicRepositoryCount !== undefined ? (
{org.publicRepositoryCount} public ) : null} {org.privateRepositoryCount !== undefined && org.privateRepositoryCount > 0 ? (
{org.privateRepositoryCount} private ) : null} {org.forkRepositoryCount !== undefined && org.forkRepositoryCount > 0 ? (
{org.forkRepositoryCount} fork{org.forkRepositoryCount !== 1 ? 's' : ''} ) : null} {/* Show a placeholder if no counts are available to maintain height */} {org.publicRepositoryCount === undefined && org.privateRepositoryCount === undefined && org.forkRepositoryCount === undefined && ( Loading counts... )} )}
{org.status === "imported" && ( )} {org.status === "mirroring" && ( )} {org.status === "mirrored" && ( )} {org.status === "failed" && ( )}
); })}
); }