import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { Plus } from 'lucide-react' import { useRouter } from 'next/router' import { parseAsBoolean, useQueryState } from 'nuqs' import { useMemo, useState } from 'react' import { toast } from 'sonner' import { Menu } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { ReportMenuItem } from './ReportMenuItem' import { CreateReportModal } from '@/components/interfaces/Reports/CreateReportModal' import { UpdateCustomReportModal } from '@/components/interfaces/Reports/UpdateModal' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { ProductMenu } from '@/components/ui/ProductMenu' import { useContentDeleteMutation } from '@/data/content/content-delete-mutation' import { Content, useContentQuery } from '@/data/content/content-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { useProfile } from '@/lib/profile' const ReportsMenu = () => { const router = useRouter() const { profile } = useProfile() const { ref, id } = useParams() const pageKey = (id || router.pathname.split('/')[4]) as string const storageSupported = useIsFeatureEnabled('project_storage:all') const { can: canCreateCustomReport } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_content', { resource: { type: 'report', owner_id: profile?.id }, subject: { id: profile?.id }, } ) // Preserve date range query parameters when navigating const preservedQueryParams = useMemo(() => { const { its, ite, isHelper, helperText } = router.query const params = new URLSearchParams() if (its && typeof its === 'string') params.set('its', its) if (ite && typeof ite === 'string') params.set('ite', ite) if (isHelper && typeof isHelper === 'string') params.set('isHelper', isHelper) if (helperText && typeof helperText === 'string') params.set('helperText', helperText) const queryString = params.toString() return queryString ? `?${queryString}` : '' }, [router.query]) const { data: content, isPending: isLoading } = useContentQuery({ projectRef: ref, type: 'report', }) const { mutate: deleteReport, isPending: isDeleting } = useContentDeleteMutation({ onSuccess: () => { setDeleteModalOpen(false) toast.success('Successfully deleted report') router.push(`/project/${ref}/reports`) }, onError: (error) => { toast.error(`Failed to delete report: ${error.message}`) }, }) const [deleteModalOpen, setDeleteModalOpen] = useState(false) const [showNewReportModal, setShowNewReportModal] = useQueryState( 'newReport', parseAsBoolean.withDefault(false).withOptions({ history: 'push', clearOnDefault: true }) ) const [selectedReportToDelete, setSelectedReportToDelete] = useState() const [selectedReportToUpdate, setSelectedReportToUpdate] = useState() const onConfirmDeleteReport = () => { if (ref === undefined) return console.error('Project ref is required') if (selectedReportToDelete?.id === undefined) return console.error('Report ID is required') deleteReport({ projectRef: ref, ids: [selectedReportToDelete.id] }) } function getReportMenuItems() { if (!content) return [] const reports = content?.content.filter((c) => c.type === 'report') const sortedReports = reports?.sort((a, b) => { if (a.name < b.name) { return -1 } if (a.name > b.name) { return 1 } return 0 }) const reportMenuItems = sortedReports.map((r, idx) => ({ id: r.id, name: r.name, description: r.description || '', key: r.id || idx + '-report', url: `/project/${ref}/reports/${r.id}${preservedQueryParams}`, hasDropdownActions: true, report: r, })) return reportMenuItems } const reportMenuItems = getReportMenuItems() const menuItems = [ { title: 'Built-in reports', key: 'builtin-reports', items: [ { name: 'API Gateway', key: 'api-overview', url: `/project/${ref}/reports/api-overview${preservedQueryParams}`, }, { name: 'Auth', key: 'auth', url: `/project/${ref}/reports/auth${preservedQueryParams}`, }, { name: 'Database', key: 'database', url: `/project/${ref}/reports/database${preservedQueryParams}`, }, { name: 'Edge Functions', key: 'edge-functions', url: `/project/${ref}/reports/edge-functions${preservedQueryParams}`, }, { name: 'Query Performance', key: 'query-performance', url: `/project/${ref}/reports/query-performance${preservedQueryParams}`, }, { name: 'PostgREST', key: 'postgrest', url: `/project/${ref}/reports/postgrest${preservedQueryParams}`, }, { name: 'Realtime', key: 'realtime', url: `/project/${ref}/reports/realtime${preservedQueryParams}`, }, ...(storageSupported ? [ { name: 'Storage', key: 'storage', url: `/project/${ref}/reports/storage${preservedQueryParams}`, }, ] : []), ], }, ] return (
{isLoading ? (
) : (
} disabled={!canCreateCustomReport} className="justify-start grow" onClick={() => { setShowNewReportModal(true) }} tooltip={{ content: { side: 'bottom', text: !canCreateCustomReport ? 'You need additional permissions to create custom reports' : undefined, }, }} > New custom report
{reportMenuItems.length > 0 ? ( Your custom reports} /> {reportMenuItems.map((item) => ( { setSelectedReportToUpdate(item.report) }} onSelectDelete={() => { setSelectedReportToDelete(item.report) setDeleteModalOpen(true) }} /> ))} ) : null} ({ ...item, items: item.items.map((subItem) => ({ ...subItem, items: [] })), }))} /> setSelectedReportToUpdate(undefined)} selectedReport={selectedReportToUpdate} initialValues={{ name: selectedReportToUpdate?.name || '', description: selectedReportToUpdate?.description || '', }} /> setDeleteModalOpen(false)} onConfirm={onConfirmDeleteReport} >

Are you sure you want to delete '{selectedReportToDelete?.name}'?

setShowNewReportModal(false)} afterSubmit={() => setShowNewReportModal(false)} />
)}
) } export default ReportsMenu