diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/@sidebar/OrganizationSidebar.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/@sidebar/OrganizationSidebar.tsx index ef47555c..ddf96576 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/@sidebar/OrganizationSidebar.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/@sidebar/OrganizationSidebar.tsx @@ -12,7 +12,7 @@ import { FreeTrialComponent } from '@/components/SubscriptionCards'; import { Skeleton } from '@/components/ui/skeleton'; import { getIsStripeTestMode } from '@/utils/server/stripe-utils'; import { differenceInDays } from 'date-fns'; -import { FileText, Home, Layers, MessageCircle, Users } from 'lucide-react'; +import { Activity, FileText, Home, Layers, MessageCircle, Users } from 'lucide-react'; const isStripeTestMode = getIsStripeTestMode() @@ -91,6 +91,11 @@ async function OrganizationSidebarInternal({ href={`/org/${organizationId}/teams`} icon={} /> + } + /> { + console.log('runs fetching'); + return getRunsByProjectId(projectId); + }, + { + refetchOnWindowFocus: false, + } + ); + + useEffect(() => { + const channel = supabaseUserClientComponentClient + .channel('digger_runs_realtime') + .on( + 'postgres_changes', + { + event: 'INSERT', + schema: 'public', + table: 'digger_runs', + filter: `project_id=eq.${projectId}` + }, + (payload) => { + refetch(); + } + ) + .on( + 'postgres_changes', + { + event: 'UPDATE', + schema: 'public', + table: 'digger_runs', + filter: `project_id=eq.${projectId}` + }, + (payload) => { + refetch(); + }, + ) + .subscribe(); + + return () => { + channel.unsubscribe(); + }; + }, [projectId, refetch]); + + /* + if (isLoading) { + return ( +
+
+ {[...Array(3)].map((_, index) => ( +
+ + + + +
+ ))} +
+
+ ); + } + + if (!runs) { + return + + Project Runs + View all runs for this project + + +

No runs found

+
+
+ } + + return ( + + + + + + Project Runs + View all runs for this project + + + + + + + + + + + ); + */ + return ( +
TEST
+ ) +} \ No newline at end of file diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/activity/AllActivityTable.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/activity/AllActivityTable.tsx new file mode 100644 index 00000000..73e5f01e --- /dev/null +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/activity/AllActivityTable.tsx @@ -0,0 +1,99 @@ +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Tables } from "@/lib/database.types"; +import { ToSnakeCase } from "@/lib/utils"; +import { AnimatePresence, motion } from "framer-motion"; +import { Activity } from "lucide-react"; +import moment from "moment"; +import Link from "next/link"; + +export type StatusColor = { + [key: string]: string; +}; + +export const statusColors: StatusColor = { + queued: 'bg-yellow-200/50 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-200', + pending_plan: 'bg-amber-200/50 text-amber-800 dark:bg-amber-900/50 dark:text-amber-200', + running_plan: 'bg-purple-200/50 text-purple-800 dark:bg-purple-900/50 dark:text-purple-200', + pending_approval: 'bg-blue-200/50 text-blue-800 dark:bg-blue-900/50 dark:text-blue-200', + approved: 'bg-green-200/50 text-green-800 dark:bg-green-900/50 dark:text-green-200', + pending_apply: 'bg-green-200/50 text-green-800 dark:bg-green-900/50 dark:text-green-200', + running_apply: 'bg-indigo-200/50 text-indigo-800 dark:bg-indigo-900/50 dark:text-indigo-200', + succeeded: 'bg-green-200/50 text-green-800 dark:bg-green-900/50 dark:text-green-200', + failed: 'bg-red-200/50 text-red-800 dark:bg-red-900/50 dark:text-red-200', + discarded: 'bg-neutral-200 text-neutral-800 dark:bg-neutral-950 dark:text-neutral-200', +}; + + +export const AllActivityTable = ({ runs, projectSlug }: { runs: Tables<'digger_runs'>[], projectSlug: string }) => { + const sortedRuns = [...runs].sort((a, b) => { + // Sort primarily by created_at in descending order (most recent first) + return moment(b.created_at).valueOf() - moment(a.created_at).valueOf(); + }); + + return ( + + + + Run ID + Commit ID + Status + Last updated + User + + + + + {sortedRuns.length > 0 ? ( + sortedRuns.map((run) => ( + + + + + {run.id.length > 8 ? `${run.id.substring(0, 8)}...` : run.id} + + + + {run.commit_id} + + + {run.status.toUpperCase()} + + + {moment(run.updated_at).fromNow()} + {run.approval_author} + + )) + ) : ( + + + + + + +

+ Runs will appear here once they are initiated. Note you need to setup your repo with digger_workflow.yml to be able to trigger runs, for more information refer to the which includes example workflow file Docs quickstart +

+
+
+
+ )} +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/activity/OrganizationTeamsTable.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/activity/OrganizationTeamsTable.tsx new file mode 100644 index 00000000..6c5add1d --- /dev/null +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/activity/OrganizationTeamsTable.tsx @@ -0,0 +1,123 @@ +"use client"; +import { T } from '@/components/ui/Typography'; +import { Card } from '@/components/ui/card'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, + type ColumnDef, + type SortingState, +} from '@tanstack/react-table'; +import moment from 'moment'; +import Link from 'next/link'; +import { useState } from 'react'; + +type TeamWithAdminName = { + adminName: string | null | undefined; + created_at: string | null; + id: number; + name: string; + organization_id: string; +}; + +type Props = { + teams: TeamWithAdminName[]; +}; + +export function OrganizationTeamsTable({ teams }: Props) { + const columns: ColumnDef[] = [ + { + accessorKey: 'name', + header: 'Name', + cell: ({ row }) => ( + + {row.getValue('name')} + + ), + }, + { + accessorKey: 'created_at', + header: 'Created At', + cell: ({ row }) => { + const date = moment(row.getValue('created_at') as string); + return date.format('MMM DD YYYY, HH:mm [hrs]'); + }, + }, + { + accessorKey: 'adminName', + header: 'Created by', + cell: ({ row }) => row.getValue('adminName') || 'unknown', + }, + ]; + + const [sorting, setSorting] = useState([]); + const table = useReactTable({ + data: teams, + columns, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onSortingChange: setSorting, + state: { + sorting, + }, + }); + + if (teams.length === 0) { + return ( + +
+ No teams found +
+
+ ); + } + + return ( +
+ + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ))} + + ))} + + + {table.getRowModel().rows.map(row => ( + + {row.getVisibleCells().map(cell => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/activity/TeamsTableWithPagination.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/activity/TeamsTableWithPagination.tsx new file mode 100644 index 00000000..41103c5d --- /dev/null +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/activity/TeamsTableWithPagination.tsx @@ -0,0 +1,44 @@ +import { Pagination } from "@/components/Pagination"; +import { T } from "@/components/ui/Typography"; +import { getTeamOwnerNameByTeamId, getTeams, getTeamsTotalCount } from "@/data/user/teams"; +import { projectsfilterSchema } from "@/utils/zod-schemas/params"; +import { OrganizationTeamsTable } from "./OrganizationTeamsTable"; + +export async function TeamsTableWithPagination({ + organizationId, + searchParams, +}: { organizationId: string; searchParams: unknown }) { + const filters = projectsfilterSchema.parse(searchParams); + + try { + const [teams, totalPages] = await Promise.all([ + getTeams({ ...filters, organizationId }), + getTeamsTotalCount({ ...filters, organizationId }), + ]); + + console.log('teams', teams); + + const adminNames = await Promise.all( + teams.map(team => getTeamOwnerNameByTeamId(team.id)) + ); + + const teamsWithAdminNames = teams.map((team, index) => ({ + ...team, + adminName: adminNames[index] || 'Unknown' + })); + + if (teamsWithAdminNames.length === 0) { + return No teams found.; + } + + return ( + <> + + + + ); + } catch (error) { + console.error('Error fetching teams data:', error); + return Error loading teams. Please try again later.; + } +} \ No newline at end of file diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/activity/page.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/activity/page.tsx new file mode 100644 index 00000000..f7a5ebc5 --- /dev/null +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/(specific-organization-pages)/activity/page.tsx @@ -0,0 +1,42 @@ +import { PageHeading } from "@/components/PageHeading"; +import { T } from "@/components/ui/Typography"; +import { + organizationParamSchema, + projectsfilterSchema +} from "@/utils/zod-schemas/params"; +import type { Metadata } from "next"; +import { Suspense } from "react"; +import type { DashboardProps } from "../page"; +import AllActivityDetails from "./AllActivityDetails"; + +export const metadata: Metadata = { + title: "Teams", + description: "You can create teams within your organization.", +}; + +export default async function Page({ + params, + searchParams, +}: DashboardProps) { + const { organizationId } = organizationParamSchema.parse(params); + const filters = projectsfilterSchema.parse(searchParams); + + return ( +
+ + + + Loading teams... + + } + > + + +
+ ); +} \ No newline at end of file diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/team/[teamId]/(specific-team-pages)/@sidebar/TeamSidebar.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/team/[teamId]/(specific-team-pages)/@sidebar/TeamSidebar.tsx index f2ccfae2..66caa77a 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/team/[teamId]/(specific-team-pages)/@sidebar/TeamSidebar.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/org/[organizationId]/team/[teamId]/(specific-team-pages)/@sidebar/TeamSidebar.tsx @@ -4,7 +4,7 @@ import { SidebarLink } from '@/components/SidebarLink'; import { fetchSlimOrganizations } from '@/data/user/organizations'; import { getOrganizationOfTeam } from '@/data/user/teams'; import { cn } from '@/utils/cn'; -import { FileText, Home, Layers, MessageCircle, Users } from 'lucide-react'; +import { Activity, FileText, Home, Layers, MessageCircle, Users } from 'lucide-react'; import { Suspense } from 'react'; async function TeamSidebarInternal({ organizationId }: { organizationId: string }) { @@ -38,6 +38,11 @@ async function TeamSidebarInternal({ organizationId }: { organizationId: string href={`/org/${organizationId}/teams`} icon={} /> + } + /> } /> + } + />