diff --git a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_de_DE.json b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_de_DE.json index 0729421729dd..b172057d5ce4 100644 --- a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_de_DE.json +++ b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_de_DE.json @@ -7,6 +7,6 @@ "status-PENDING": "Ausstehend", "status-UPDATING": "Update", "status-ERROR": "Fehler", - "status-ERROR_INCONSISTENT_SPEC": "Fehler", + "status-ERROR_INCONSISTENT_SPEC": "SPEC-Fehler", "status-READY": "Verfügbar" } diff --git a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_en_GB.json b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_en_GB.json index 764e6a97de7e..6401d2a8710b 100644 --- a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_en_GB.json +++ b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_en_GB.json @@ -7,6 +7,6 @@ "status-PENDING": "Queued", "status-UPDATING": "Updating", "status-ERROR": "Error", - "status-ERROR_INCONSISTENT_SPEC": "Error", + "status-ERROR_INCONSISTENT_SPEC": "spec error", "status-READY": "Available" } diff --git a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_es_ES.json b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_es_ES.json index 8f8b4de52d89..b877d2dd96ab 100644 --- a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_es_ES.json +++ b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_es_ES.json @@ -7,6 +7,6 @@ "status-PENDING": "Pendiente", "status-UPDATING": "Actualizada", "status-ERROR": "En error", - "status-ERROR_INCONSISTENT_SPEC": "En error", + "status-ERROR_INCONSISTENT_SPEC": "Error spec", "status-READY": "Disponible" } diff --git a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_fr_CA.json b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_fr_CA.json index 3fb4103c8d6d..505269b2fb63 100644 --- a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_fr_CA.json +++ b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_fr_CA.json @@ -7,6 +7,6 @@ "status-PENDING": "En attente", "status-UPDATING": "Mise à jour", "status-ERROR": "En erreur", - "status-ERROR_INCONSISTENT_SPEC": "En erreur", + "status-ERROR_INCONSISTENT_SPEC": "Erreur spec", "status-READY": "Disponible" } diff --git a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_fr_FR.json b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_fr_FR.json index 3fb4103c8d6d..505269b2fb63 100644 --- a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_fr_FR.json +++ b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_fr_FR.json @@ -7,6 +7,6 @@ "status-PENDING": "En attente", "status-UPDATING": "Mise à jour", "status-ERROR": "En erreur", - "status-ERROR_INCONSISTENT_SPEC": "En erreur", + "status-ERROR_INCONSISTENT_SPEC": "Erreur spec", "status-READY": "Disponible" } diff --git a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_it_IT.json b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_it_IT.json index 493984f467c9..6a858d939a50 100644 --- a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_it_IT.json +++ b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_it_IT.json @@ -7,6 +7,6 @@ "status-PENDING": "In attesa", "status-UPDATING": "Aggiornamento", "status-ERROR": "In errore", - "status-ERROR_INCONSISTENT_SPEC": "In errore", + "status-ERROR_INCONSISTENT_SPEC": "Errore specifico", "status-READY": "Disponibile" } diff --git a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_pl_PL.json b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_pl_PL.json index 961c482b5bba..e03b6692d28d 100644 --- a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_pl_PL.json +++ b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_pl_PL.json @@ -7,6 +7,6 @@ "status-PENDING": "Oczekujące", "status-UPDATING": "Aktualizacja", "status-ERROR": "Błąd", - "status-ERROR_INCONSISTENT_SPEC": "Błąd", + "status-ERROR_INCONSISTENT_SPEC": "Błąd spec", "status-READY": "Dostępny" } diff --git a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_pt_PT.json b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_pt_PT.json index 7ceef0c6af7c..2b6259a40129 100644 --- a/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_pt_PT.json +++ b/packages/manager/apps/pci-databases-analytics/public/translations/pci-databases-analytics/services/status/Messages_pt_PT.json @@ -7,6 +7,6 @@ "status-PENDING": "Em espera", "status-UPDATING": "Atualização", "status-ERROR": "Erro", - "status-ERROR_INCONSISTENT_SPEC": "Erro", + "status-ERROR_INCONSISTENT_SPEC": "Erro spec", "status-READY": "Disponível" } diff --git a/packages/manager/apps/pci-databases-analytics/src/__tests__/setupTest.ts b/packages/manager/apps/pci-databases-analytics/src/__tests__/setupTest.ts index 8cd2e90548c9..54cc6e9f3023 100644 --- a/packages/manager/apps/pci-databases-analytics/src/__tests__/setupTest.ts +++ b/packages/manager/apps/pci-databases-analytics/src/__tests__/setupTest.ts @@ -5,3 +5,12 @@ import { PointerEvent } from './helpers/pointerEvent'; // it is requiered for DropdownMenus // source: https://github.com/radix-ui/primitives/issues/856#issuecomment-928704064 window.PointerEvent = PointerEvent as any; + +const originalConsoleError = console.error; + +console.error = (...args) => { + if (typeof args[0] === 'string' && args[0].includes('connect ECONNREFUSED')) { + return; + } + originalConsoleError(...args); +}; diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/DataTable.component.tsx b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DataTable.component.tsx new file mode 100644 index 000000000000..cd82626bccef --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DataTable.component.tsx @@ -0,0 +1,123 @@ +import React, { ReactElement, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { flexRender } from '@tanstack/react-table'; +import { ChevronDown, ChevronUp } from 'lucide-react'; +import { useDataTableContext } from './DataTable.context'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { Button } from '../ui/button'; + +export const MENU_COLUMN_ID = 'actions_menu_column'; + +interface DatatableProps { + renderRowExpansion?: (row: TData) => ReactElement | null; +} + +export function DataTable({ + renderRowExpansion, +}: DatatableProps) { + const { table, rows } = useDataTableContext(); + const { t } = useTranslation('pci-databases-analytics/components/data-table'); + const [expandedRows, setExpandedRows] = useState>({}); + + const toggleRowExpansion = (rowId: string) => { + setExpandedRows((prev) => ({ + ...prev, + [rowId]: !prev[rowId], + })); + }; + + const headerGroups = table.getHeaderGroups(); + return ( + + + {headerGroups.map((headerGroup) => ( + + {renderRowExpansion && ( + + )} + {headerGroup.headers.map((header, index) => { + const isEmptyHeader = header.id === MENU_COLUMN_ID; + // Get a reference to the previous header + const isEmptyNextHeader = + headerGroup.headers[index + 1]?.id === MENU_COLUMN_ID; + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {rows?.length ? ( + rows.map((row) => ( + + + {renderRowExpansion && ( + + + + )} + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + {expandedRows[row.id] && renderRowExpansion && ( + + + {renderRowExpansion(row.original as TData)} + + + )} + + )) + ) : ( + + + {t('noResult')} + + + )} + +
+ ); +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/DataTable.context.tsx b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DataTable.context.tsx new file mode 100644 index 000000000000..c875393e735c --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DataTable.context.tsx @@ -0,0 +1,122 @@ +import { + ColumnDef, + Row, + SortingState, + Table, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from '@tanstack/react-table'; +import { ReactNode, createContext, useContext, useMemo, useState } from 'react'; +import { useColumnFilters } from './useColumnFilters.hook'; +import { applyFilters } from '@/lib/filters'; +import { ColumnFilter } from './DatatableDefaultFilterButton.component'; +import { DataTable } from './DataTable.component'; +import { DataTablePagination } from './DatatablePagination.component'; + +interface DataTableProviderProps { + columns: ColumnDef[]; + data: TData[]; + pageSize?: number; + itemNumber?: number; + filtersDefinition?: ColumnFilter[]; + children?: ReactNode; +} + +interface DataTableContextValue { + table: Table; + filtersDefinition?: ColumnFilter[]; + columnFilters: ReturnType; + globalFilter: string; + data: TData[]; + filteredData: TData[]; + sorting: SortingState; + rows: Row[]; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const DataTableContext = createContext | null>(null); + +export function DataTableProvider({ + columns, + data, + pageSize, + filtersDefinition, + children, +}: DataTableProviderProps) { + const [sorting, setSorting] = useState([ + { + id: columns[0]?.id as string, + desc: false, + }, + ]); + const [globalFilter, setGlobalFilter] = useState(''); + const columnFilters = useColumnFilters(); + + const filteredData = useMemo( + () => applyFilters(data || [], columnFilters.filters) as TData[], + [columnFilters.filters, data], + ); + const table = useReactTable({ + data: filteredData, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + state: { + sorting, + globalFilter, + }, + initialState: { + pagination: { pageSize: pageSize ?? 5 }, + }, + onGlobalFilterChange: (e) => { + setGlobalFilter(e); + }, + globalFilterFn: 'auto', + }); + + const rows = useMemo(() => table.getRowModel()?.rows, [ + table, + globalFilter, + columnFilters.filters, + data, + sorting, + ]); + + const contextValue: DataTableContextValue = { + table, + filtersDefinition, + columnFilters, + globalFilter, + data, + filteredData, + sorting, + rows, + }; + + return ( + + {children || ( + <> + + + + )} + + ); +} + +export function useDataTableContext() { + const context = useContext>(DataTableContext); + if (!context) { + throw new Error( + 'useDataTableContext must be used within a DataTableProvider', + ); + } + return context as DataTableContextValue; +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableAction.component.tsx b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableAction.component.tsx new file mode 100644 index 000000000000..1164c90e38a7 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableAction.component.tsx @@ -0,0 +1,5 @@ +import { ReactNode } from 'react'; + +export function DatatableAction({ children }: { children: ReactNode }) { + return <>{children || <>}; +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableDefaultFilterButton.component.tsx b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableDefaultFilterButton.component.tsx new file mode 100644 index 000000000000..59af07312137 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableDefaultFilterButton.component.tsx @@ -0,0 +1,227 @@ +import { useTranslation } from 'react-i18next'; +import { ReactNode, useEffect, useMemo, useState } from 'react'; +import { CalendarIcon, FilterIcon } from 'lucide-react'; +import { Input } from '../ui/input'; +import { Label } from '../ui/label'; +import { Button } from '../ui/button'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '../ui/select'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import './translations'; +import { Calendar } from '../ui/calendar'; +import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; +import FormattedDate from '../formatted-date/FormattedDate.component'; +import { useDateFnsLocale } from '@/hooks/useDateFnsLocale.hook'; +import { Filter, FilterCategories, FilterComparator } from '@/lib/filters'; + +export type ColumnFilter = { + id: string; + label: string; + comparators: FilterComparator[]; + options?: { label: string | ReactNode; value: string }[]; +}; + +export type DataTableDefaultFilterButtonProps = { + columns: ColumnFilter[]; + onAddFilter: (filter: Filter, column: ColumnFilter) => void; +}; +const DataTableDefaultFilterButton = ({ + columns, + onAddFilter, +}: DataTableDefaultFilterButtonProps) => { + const { t } = useTranslation('filters'); + const dateLocale = useDateFnsLocale(); + const [filtersOpen, setFiltersOpen] = useState(false); + + const [selectedId, setSelectedId] = useState(columns?.[0]?.id || ''); + const [selectedComparator, setSelectedComparator] = useState( + columns?.[0]?.comparators?.[0] || FilterComparator.IsEqual, + ); + const [value, setValue] = useState(''); + const [dateValue, setDateValue] = useState(new Date()); + + const selectedColumn = useMemo( + () => columns.find(({ id }) => selectedId === id), + [columns, selectedId], + ); + + useEffect(() => { + if (!selectedColumn.comparators.includes(selectedComparator)) { + setSelectedComparator(selectedColumn.comparators[0]); + } + }, [selectedColumn]); + + const isInputDate = selectedColumn?.comparators === FilterCategories.Date; + const isInputNumeric = + selectedColumn?.comparators === FilterCategories.Numeric; + const isInputSelect = + selectedColumn?.comparators === FilterCategories.Options && + selectedColumn?.options?.length > 0; + const isInputString = + selectedColumn?.comparators === FilterCategories.String && !isInputSelect; + + const submitAddFilter = () => { + onAddFilter( + { + key: selectedId, + comparator: selectedComparator, + value: isInputDate ? dateValue.toString() : value, + }, + selectedColumn, + ); + setValue(''); + setDateValue(new Date()); + setFiltersOpen(false); + setSelectedId(columns[0].id); + }; + + return ( + + + + + +
+ + +
+
+ + +
+
+ + {isInputDate && ( + + + + + + setDateValue(day)} + locale={dateLocale} + initialFocus + /> + + + )} + {isInputSelect && ( + + )} + {isInputString && ( + setValue(`${e.target.value}`)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + submitAddFilter(); + } + }} + /> + )} + {isInputNumeric && ( + setValue(`${e.target.value}`)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + submitAddFilter(); + } + }} + /> + )} +
+
+ +
+
+
+ ); +}; + +export default DataTableDefaultFilterButton; diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableFiltersButton.component.tsx b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableFiltersButton.component.tsx new file mode 100644 index 000000000000..59f189303719 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableFiltersButton.component.tsx @@ -0,0 +1,23 @@ +import { ReactNode } from 'react'; +import { useDataTableContext } from './DataTable.context'; +import DataTableDefaultFilterButton from './DatatableDefaultFilterButton.component'; + +export function DatatableFiltersButton({ children }: { children?: ReactNode }) { + const { filtersDefinition, columnFilters } = useDataTableContext(); + if (!filtersDefinition?.length) return <>; + return ( + <> + {children || ( + { + columnFilters.addFilter({ + ...addedFilter, + label: column.label, + }); + }} + /> + )} + + ); +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableFiltersList.component.tsx b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableFiltersList.component.tsx new file mode 100644 index 000000000000..8e915dd699ef --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableFiltersList.component.tsx @@ -0,0 +1,90 @@ +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { X } from 'lucide-react'; +import { Badge } from '../ui/badge'; +import { Button } from '../ui/button'; +import './translations'; +import { useLocale } from '@/hooks/useLocale'; +import { useDataTableContext } from './DataTable.context'; + +export declare enum FilterComparator { + Includes = 'includes', + StartsWith = 'starts_with', + EndsWith = 'ends_with', + IsEqual = 'is_equal', + IsDifferent = 'is_different', + IsLower = 'is_lower', + IsHigher = 'is_higher', + IsBefore = 'is_before', + IsAfter = 'is_after', + IsIn = 'is_in', +} + +export type Filter = { + key: string; + value: string | string[]; + comparator: FilterComparator; +}; + +export type FilterWithLabel = Filter & { label: string }; + +export type FilterListProps = { + filters: FilterWithLabel[]; + onRemoveFilter: (filter: FilterWithLabel) => void; +}; + +export function DatatableFiltersList() { + const { t } = useTranslation('filters'); + const { columnFilters } = useDataTableContext(); + const locale = useLocale(); + const formater = useMemo( + () => new Intl.DateTimeFormat(locale.replace('_', '-')), + [locale], + ); + const tComp = (comparator: string) => + t(`common_criteria_adder_operator_${comparator}`); + + const isValidDate = (value: string): boolean => { + // Reject purely numerical strings + if (!Number.isNaN(Number(value))) { + return false; + } + const date = new Date(value); + return !Number.isNaN(date.getTime()); + }; + const getFilterContent = (filter: FilterWithLabel) => { + const label = `${ + filter.label ? `${filter.label} ${tComp(filter.comparator)} ` : '' + }`; + let formattedValue; + if (Array.isArray(filter.value)) { + // Handle array values + formattedValue = filter.value.join(', '); + } else if (typeof filter.value === 'string' && isValidDate(filter.value)) { + // Format valid date strings + formattedValue = formater.format(new Date(filter.value)); + } else { + // Fallback for other types of strings or invalid dates + formattedValue = filter.value; + } + return `${label}${formattedValue}`; + }; + + if (!columnFilters?.filters.length) return <>; + return ( +
+ {columnFilters.filters?.map((filter, key) => ( + + {getFilterContent(filter)} + + + ))} +
+ ); +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableHeader.component.tsx b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableHeader.component.tsx new file mode 100644 index 000000000000..61882c389ca1 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableHeader.component.tsx @@ -0,0 +1,32 @@ +import { Children, ReactElement, ReactNode } from 'react'; +import DataTable from './index'; + +export function DatatableHeader({ children }: { children: ReactNode }) { + // Helper function to check if a child is a ReactElement + const isReactElement = (child: ReactNode): child is ReactElement => + !!child && typeof child === 'object' && 'type' in child; + + // Categorize children into `actionButton`, `searchbar`, and `filters` + const actionButton = Children.toArray(children).find( + (child): child is ReactElement => + isReactElement(child) && child.type === DataTable.Action, + ); + const searchbar = Children.toArray(children).find( + (child): child is ReactElement => + isReactElement(child) && child.type === DataTable.SearchBar, + ); + const filters = Children.toArray(children).find( + (child): child is ReactElement => + isReactElement(child) && child.type === DataTable.FiltersButton, + ); + + return ( +
+ {actionButton} +
+ {searchbar} + {filters} +
+
+ ); +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatablePagination.component.tsx b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatablePagination.component.tsx new file mode 100644 index 000000000000..ea14a94ba36e --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatablePagination.component.tsx @@ -0,0 +1,99 @@ +import { + ChevronLeft, + ChevronRight, + ChevronFirst, + ChevronLast, +} from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { Button } from '@/components/ui/button'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '../ui/select'; +import { useDataTableContext } from './DataTable.context'; + +export function DataTablePagination() { + const { t } = useTranslation('pci-databases-analytics/components/data-table'); + const { table } = useDataTableContext(); + const itemCount = table.getRowCount(); + if (itemCount === 0) return <>; + return ( +
+
+
+ {itemCount > 0 && ( +
+ {t('itemCount', { count: itemCount })} +
+ )} + +
+
+ + {t('currentPage', { + currentPage: table.getState().pagination.pageIndex + 1, + totalPagesCount: table.getPageCount(), + })} + +
+
+ + + + +
+
+
+ ); +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableSearchBar.component.tsx b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableSearchBar.component.tsx new file mode 100644 index 000000000000..b82f7f0a8776 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableSearchBar.component.tsx @@ -0,0 +1,29 @@ +import { ReactNode } from 'react'; +import { Search } from 'lucide-react'; +import { Input } from '../ui/input'; +import { Button } from '../ui/button'; +import { useDataTableContext } from './DataTable.context'; + +export function DatatableSearchBar({ children }: { children?: ReactNode }) { + const { table, globalFilter } = useDataTableContext(); + return ( + <> + {children || ( +
+ table.setGlobalFilter(String(e.target.value))} + placeholder="Search..." + className="max-w-full sm:max-w-72 rounded-r-none focus-visible:ring-transparent focus-visible:bg-primary-50" + /> + +
+ )} + + ); +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableSkeleton.component.tsx b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableSkeleton.component.tsx new file mode 100644 index 000000000000..89a52b1cf2e8 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableSkeleton.component.tsx @@ -0,0 +1,56 @@ +import { Skeleton } from '../ui/skeleton'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; + +interface DataTableSkeletonProps { + rows?: number; + columns?: number; + height?: number; + width?: number; +} +export function DatatableSkeleton({ + height = 16, + width = 80, + rows = 5, + columns = 5, +}: DataTableSkeletonProps) { + return ( + + + + {Array.from({ length: columns }).map((colHead, iColHead) => ( + + + + ))} + + + + {Array.from({ length: rows }).map((row, iRow) => ( + + {Array.from({ length: columns }).map((col, iCol) => ( + + + + ))} + + ))} + +
+ ); +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableSortableHeader.component.tsx b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableSortableHeader.component.tsx new file mode 100644 index 000000000000..92ecfc22e7b9 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/DatatableSortableHeader.component.tsx @@ -0,0 +1,36 @@ +import { ChevronDown, ChevronUp, ChevronsUpDown } from 'lucide-react'; +import { SortingColumn } from '@tanstack/react-table'; +import { Button } from '@/components/ui/button'; + +interface SortableHeaderProps { + column: SortingColumn; + children: React.ReactNode; +} +export function DatatableSortableHeader({ + column, + children, +}: SortableHeaderProps) { + const sort = column.getIsSorted(); + let icon = ; + if (sort === 'asc') { + icon = ; + } else if (sort === 'desc') { + icon = ; + } + + const buttonClass = `px-0 font-semibold ${ + sort + ? 'text-primary-500 hover:text-primary-500' + : 'text-primary-800 hover:text-primary-800' + }`; + return ( + + ); +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/index.ts b/packages/manager/apps/pci-databases-analytics/src/components/data-table/index.ts new file mode 100644 index 000000000000..c1c24370e9e5 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/index.ts @@ -0,0 +1,25 @@ +import { DataTable as Table } from './DataTable.component'; +import { DataTableProvider } from './DataTable.context'; +import { DatatableAction } from './DatatableAction.component'; +import { DatatableFiltersButton } from './DatatableFiltersButton.component'; +import { DatatableFiltersList } from './DatatableFiltersList.component'; +import { DatatableHeader } from './DatatableHeader.component'; +import { DataTablePagination } from './DatatablePagination.component'; +import { DatatableSearchBar } from './DatatableSearchBar.component'; +import { DatatableSkeleton } from './DatatableSkeleton.component'; +import { DatatableSortableHeader } from './DatatableSortableHeader.component'; + +const DataTable = { + Provider: DataTableProvider, + Header: DatatableHeader, + Action: DatatableAction, + SearchBar: DatatableSearchBar, + FiltersButton: DatatableFiltersButton, + FiltersList: DatatableFiltersList, + Table, + Pagination: DataTablePagination, + Skeleton: DatatableSkeleton, + SortableHeader: DatatableSortableHeader, +}; + +export default DataTable; diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_de_DE.json b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_de_DE.json new file mode 100644 index 000000000000..ca298dabe59a --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_de_DE.json @@ -0,0 +1,18 @@ +{ + "common_criteria_adder_filter_label": "Filtern", + "common_criteria_adder_column_label": "Spalte", + "common_criteria_adder_operator_label": "Bedingung", + "common_criteria_adder_operator_includes": "enthält", + "common_criteria_adder_operator_starts_with": "Beginnt mit", + "common_criteria_adder_operator_ends_with": "Endet mit", + "common_criteria_adder_operator_is_equal": "ist gleich", + "common_criteria_adder_operator_is_different": "ist nicht gleich", + "common_criteria_adder_operator_is_lower": "ist kleiner als", + "common_criteria_adder_operator_is_higher": "ist größer als", + "common_criteria_adder_operator_is_before": "ist davor", + "common_criteria_adder_operator_is_after": "Ist nach", + "common_criteria_adder_true_label": "Ja", + "common_criteria_adder_false_label": "Nein", + "common_criteria_adder_value_label": "Wert", + "common_criteria_adder_submit_label": "Hinzufügen" +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_en_GB.json b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_en_GB.json new file mode 100644 index 000000000000..185ec0041733 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_en_GB.json @@ -0,0 +1,18 @@ +{ + "common_criteria_adder_filter_label": "Sort", + "common_criteria_adder_column_label": "Column", + "common_criteria_adder_operator_label": "Condition", + "common_criteria_adder_operator_includes": "contains", + "common_criteria_adder_operator_starts_with": "starts with", + "common_criteria_adder_operator_ends_with": "ends with", + "common_criteria_adder_operator_is_equal": "equals", + "common_criteria_adder_operator_is_different": "is not equal to", + "common_criteria_adder_operator_is_lower": "is less than", + "common_criteria_adder_operator_is_higher": "is greater than", + "common_criteria_adder_operator_is_before": "is before", + "common_criteria_adder_operator_is_after": "is after", + "common_criteria_adder_true_label": "Yes", + "common_criteria_adder_false_label": "No", + "common_criteria_adder_value_label": "Value", + "common_criteria_adder_submit_label": "Add" +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_es_ES.json b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_es_ES.json new file mode 100644 index 000000000000..c2ae6a5bf8a2 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_es_ES.json @@ -0,0 +1,18 @@ +{ + "common_criteria_adder_filter_label": "Filtrar", + "common_criteria_adder_column_label": "Columna", + "common_criteria_adder_operator_label": "Condición", + "common_criteria_adder_operator_includes": "contiene", + "common_criteria_adder_operator_starts_with": "empieza por", + "common_criteria_adder_operator_ends_with": "terminado por", + "common_criteria_adder_operator_is_equal": "es igual a", + "common_criteria_adder_operator_is_different": "es diferente de", + "common_criteria_adder_operator_is_lower": "es inferior a", + "common_criteria_adder_operator_is_higher": "es superior a", + "common_criteria_adder_operator_is_before": "es anterior", + "common_criteria_adder_operator_is_after": "es posterior a", + "common_criteria_adder_true_label": "Sí", + "common_criteria_adder_false_label": "No", + "common_criteria_adder_value_label": "Valor", + "common_criteria_adder_submit_label": "Añadir" +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_fr_CA.json b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_fr_CA.json new file mode 100644 index 000000000000..b7a0bf321471 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_fr_CA.json @@ -0,0 +1,18 @@ +{ + "common_criteria_adder_filter_label": "Filtrer", + "common_criteria_adder_column_label": "Colonne", + "common_criteria_adder_operator_label": "Condition", + "common_criteria_adder_operator_includes": "contient", + "common_criteria_adder_operator_starts_with": "débute par", + "common_criteria_adder_operator_ends_with": "termine par", + "common_criteria_adder_operator_is_equal": "est égal à", + "common_criteria_adder_operator_is_different": "est différent de", + "common_criteria_adder_operator_is_lower": "est inférieur à", + "common_criteria_adder_operator_is_higher": "est supérieur à", + "common_criteria_adder_operator_is_before": "est avant", + "common_criteria_adder_operator_is_after": "est après", + "common_criteria_adder_true_label": "Oui", + "common_criteria_adder_false_label": "Non", + "common_criteria_adder_value_label": "Valeur", + "common_criteria_adder_submit_label": "Ajouter" +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_fr_FR.json b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_fr_FR.json new file mode 100644 index 000000000000..b7a0bf321471 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_fr_FR.json @@ -0,0 +1,18 @@ +{ + "common_criteria_adder_filter_label": "Filtrer", + "common_criteria_adder_column_label": "Colonne", + "common_criteria_adder_operator_label": "Condition", + "common_criteria_adder_operator_includes": "contient", + "common_criteria_adder_operator_starts_with": "débute par", + "common_criteria_adder_operator_ends_with": "termine par", + "common_criteria_adder_operator_is_equal": "est égal à", + "common_criteria_adder_operator_is_different": "est différent de", + "common_criteria_adder_operator_is_lower": "est inférieur à", + "common_criteria_adder_operator_is_higher": "est supérieur à", + "common_criteria_adder_operator_is_before": "est avant", + "common_criteria_adder_operator_is_after": "est après", + "common_criteria_adder_true_label": "Oui", + "common_criteria_adder_false_label": "Non", + "common_criteria_adder_value_label": "Valeur", + "common_criteria_adder_submit_label": "Ajouter" +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_it_IT.json b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_it_IT.json new file mode 100644 index 000000000000..5234ecc7f0b8 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_it_IT.json @@ -0,0 +1,18 @@ +{ + "common_criteria_adder_filter_label": "Aggiungi filtri", + "common_criteria_adder_column_label": "Colonna", + "common_criteria_adder_operator_label": "Condizione", + "common_criteria_adder_operator_includes": "contiene", + "common_criteria_adder_operator_starts_with": "inizia per", + "common_criteria_adder_operator_ends_with": "termina per", + "common_criteria_adder_operator_is_equal": "è uguale a", + "common_criteria_adder_operator_is_different": "è diverso da", + "common_criteria_adder_operator_is_lower": "è inferiore a", + "common_criteria_adder_operator_is_higher": "è superiore a", + "common_criteria_adder_operator_is_before": "è prima", + "common_criteria_adder_operator_is_after": "è dopo", + "common_criteria_adder_true_label": "Sì", + "common_criteria_adder_false_label": "No", + "common_criteria_adder_value_label": "Valore", + "common_criteria_adder_submit_label": "Aggiungi" +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_pl_PL.json b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_pl_PL.json new file mode 100644 index 000000000000..92eb00f8a53a --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_pl_PL.json @@ -0,0 +1,18 @@ +{ + "common_criteria_adder_filter_label": "Filtruj", + "common_criteria_adder_column_label": "Kolumna", + "common_criteria_adder_operator_label": "Warunek", + "common_criteria_adder_operator_includes": "zawiera", + "common_criteria_adder_operator_starts_with": "rozpoczyna się od", + "common_criteria_adder_operator_ends_with": "kończy się na", + "common_criteria_adder_operator_is_equal": "równa się", + "common_criteria_adder_operator_is_different": "nie jest", + "common_criteria_adder_operator_is_lower": "jest mniejszy niż", + "common_criteria_adder_operator_is_higher": "jest większy niż", + "common_criteria_adder_operator_is_before": "jest przed", + "common_criteria_adder_operator_is_after": "jest po", + "common_criteria_adder_true_label": "Tak", + "common_criteria_adder_false_label": "Nie", + "common_criteria_adder_value_label": "Wartość", + "common_criteria_adder_submit_label": "Dodaj" +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_pt_PT.json b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_pt_PT.json new file mode 100644 index 000000000000..0cac51c6c038 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/Messages_pt_PT.json @@ -0,0 +1,18 @@ +{ + "common_criteria_adder_filter_label": "Filtrar", + "common_criteria_adder_column_label": "Coluna", + "common_criteria_adder_operator_label": "Condição", + "common_criteria_adder_operator_includes": "contém", + "common_criteria_adder_operator_starts_with": "começa por", + "common_criteria_adder_operator_ends_with": "termina em", + "common_criteria_adder_operator_is_equal": "é igual a", + "common_criteria_adder_operator_is_different": "é diferente de", + "common_criteria_adder_operator_is_lower": "é inferior a", + "common_criteria_adder_operator_is_higher": "é superior a", + "common_criteria_adder_operator_is_before": "está antes", + "common_criteria_adder_operator_is_after": "está depois", + "common_criteria_adder_true_label": "Sim", + "common_criteria_adder_false_label": "Não", + "common_criteria_adder_value_label": "Valor", + "common_criteria_adder_submit_label": "Adicionar" +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/index.ts b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/index.ts new file mode 100644 index 000000000000..68cffeea719d --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/translations/index.ts @@ -0,0 +1,27 @@ +import i18next from 'i18next'; + +import de_DE from './Messages_de_DE.json'; +import en_GB from './Messages_en_GB.json'; +import es_ES from './Messages_es_ES.json'; +import fr_CA from './Messages_fr_CA.json'; +import fr_FR from './Messages_fr_FR.json'; +import it_IT from './Messages_it_IT.json'; +import pl_PL from './Messages_pl_PL.json'; +import pt_PT from './Messages_pt_PT.json'; + +function addTranslations() { + i18next.addResources('de_DE', 'filters', de_DE); + i18next.addResources('en_GB', 'filters', en_GB); + i18next.addResources('es_ES', 'filters', es_ES); + i18next.addResources('fr_CA', 'filters', fr_CA); + i18next.addResources('fr_FR', 'filters', fr_FR); + i18next.addResources('it_IT', 'filters', it_IT); + i18next.addResources('pl_PL', 'filters', pl_PL); + i18next.addResources('pt_PT', 'filters', pt_PT); +} + +if (i18next.isInitialized) { + addTranslations(); +} else { + i18next.on('initialized', addTranslations); +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/data-table/useColumnFilters.hook.tsx b/packages/manager/apps/pci-databases-analytics/src/components/data-table/useColumnFilters.hook.tsx new file mode 100644 index 000000000000..f9bcda9b1cb3 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/components/data-table/useColumnFilters.hook.tsx @@ -0,0 +1,32 @@ +import { useState } from 'react'; +import { Filter } from '@/lib/filters'; +import { FilterWithLabel } from './DatatableFiltersList.component'; + +const filterEquals = (a: Filter, b: Filter) => + a.key === b.key && a.value === b.value && a.comparator === b.comparator; + +export function useColumnFilters() { + const [filters, setFilters] = useState([]); + + return { + filters, + addFilter: (filter: FilterWithLabel) => { + if (filter.value) { + setFilters((previousFilters) => { + /** + * ? To remove the duplication from the filters + */ + if (previousFilters.some((f) => filterEquals(f, filter))) { + return previousFilters; + } + return [...previousFilters, filter]; + }); + } + }, + removeFilter: (filter: Filter) => { + setFilters((previousFilters) => + previousFilters.filter((f) => !filterEquals(f, filter)), + ); + }, + }; +} diff --git a/packages/manager/apps/pci-databases-analytics/src/components/ui/button.tsx b/packages/manager/apps/pci-databases-analytics/src/components/ui/button.tsx index 57f4a373a512..4791dca2ccf3 100644 --- a/packages/manager/apps/pci-databases-analytics/src/components/ui/button.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/components/ui/button.tsx @@ -9,7 +9,7 @@ const buttonVariants = cva( { variants: { variant: { - default: "bg-primary text-primary-foreground hover:bg-primary-600", + default: "bg-primary text-primary-foreground hover:text-primary-foreground hover:bg-primary-600", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: @@ -18,7 +18,7 @@ const buttonVariants = cva( "border text-primary border-primary border-2 bg-background font-semibold hover:bg-primary-100 rounded-full", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", + ghost: "hover:text-accent-foreground", table: "hover:bg-primary-100 hover:text-primary-700 hover:font-semibold", link: "text-primary underline-offset-4 hover:underline", input: "border border-input bg-background", diff --git a/packages/manager/apps/pci-databases-analytics/src/components/ui/data-table.tsx b/packages/manager/apps/pci-databases-analytics/src/components/ui/data-table.tsx deleted file mode 100644 index 31c9a8cd9149..000000000000 --- a/packages/manager/apps/pci-databases-analytics/src/components/ui/data-table.tsx +++ /dev/null @@ -1,273 +0,0 @@ -import { useState } from 'react'; -import { ChevronDown, ChevronUp, ChevronsUpDown, ChevronLeft, ChevronRight, ChevronFirst, ChevronLast } from 'lucide-react'; -import { - ColumnDef, - SortingColumn, - SortingState, - flexRender, - Table as TanStackTable, - getCoreRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from '@tanstack/react-table'; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from '@/components/ui/table'; -import { Button } from '@/components/ui/button'; -import { Skeleton } from './skeleton'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './select'; -import { useTranslation } from 'react-i18next'; - -interface DataTablePaginationProps { - table: TanStackTable - itemNumber?: number -} -export function DataTablePagination({ - table, - itemNumber, -}: DataTablePaginationProps) { - const { t } = useTranslation('pci-databases-analytics/components/data-table'); - return ( -
-
-
- {itemNumber > 0 && ( -
- {t('itemCount', { count: itemNumber })} -
- )} - -
-
- {t('currentPage', { currentPage: table.getState().pagination.pageIndex + 1, totalPagesCount: table.getPageCount()})} -
-
- - - - -
-
-
- ); -} - -interface DataTableProps { - columns: ColumnDef[]; - data: TData[]; - pageSize?: number; - itemNumber?: number; -} - -export function DataTable({ - columns, - data, - pageSize, - itemNumber, -}: DataTableProps) { - const { t } = useTranslation('pci-databases-analytics/components/data-table'); - const [sorting, setSorting] = useState([]); - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - onSortingChange: setSorting, - getSortedRowModel: getSortedRowModel(), - state: { - sorting, - }, - initialState: { - pagination: { pageSize: pageSize ?? 5 }, - }, - }); - - return ( - <> -
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext(), - )} - - ); - })} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ))} - - )) - ) : ( - - - {t('noResult')} - - - )} - -
-
- - - ); -} - -interface SortableHeaderProps { - column: SortingColumn; - children: React.ReactNode; -} -export function SortableHeader({ - column, - children, -}: SortableHeaderProps) { - const sort = column.getIsSorted(); - let icon = ; - if (sort === 'asc') { - icon = ; - } else if (sort === 'desc') { - icon = ; - } - - const buttonClass = `px-0 font-bold hover:bg-primary-100 ${ - sort - ? 'text-primary-500 hover:text-primary-500' - : 'text-primary-700 hover:text-primary-500' - }`; - return ( - - ); -} - -interface DataTableSkeletonProps { - columns?: number; - rows?: number; - height?: number; - width?: number; -} -DataTable.Skeleton = function DataTableSkeleton({ - columns = 5, - rows = 5, - height = 16, - width = 80, -}: DataTableSkeletonProps) { - return ( - - - - {Array.from({ length: columns }).map((colHead, iColHead) => ( - - - - ))} - - - - {Array.from({ length: rows }).map((row, iRow) => ( - - {Array.from({ length: columns }).map((col, iCol) => ( - - - - ))} - - ))} - -
- ); -}; \ No newline at end of file diff --git a/packages/manager/apps/pci-databases-analytics/src/lib/filters.ts b/packages/manager/apps/pci-databases-analytics/src/lib/filters.ts new file mode 100644 index 000000000000..29924ac1be86 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/lib/filters.ts @@ -0,0 +1,99 @@ +export enum FilterComparator { + Includes = 'includes', + StartsWith = 'starts_with', + EndsWith = 'ends_with', + IsEqual = 'is_equal', + IsDifferent = 'is_different', + IsLower = 'is_lower', + IsHigher = 'is_higher', + IsBefore = 'is_before', + IsAfter = 'is_after', + IsIn = 'is_in', +} + +export type Filter = { + key: string; + value: string | string[]; + comparator: FilterComparator; +}; + +export const FilterCategories = { + Options: [FilterComparator.IsEqual, FilterComparator.IsDifferent], + Numeric: [ + FilterComparator.IsEqual, + FilterComparator.IsDifferent, + FilterComparator.IsLower, + FilterComparator.IsHigher, + ], + String: [ + FilterComparator.Includes, + FilterComparator.StartsWith, + FilterComparator.EndsWith, + FilterComparator.IsEqual, + FilterComparator.IsDifferent, + ], + Date: [ + FilterComparator.IsEqual, + FilterComparator.IsDifferent, + FilterComparator.IsBefore, + FilterComparator.IsAfter, + ], +}; + +export function applyFilters(items: T[] = [], filters: Filter[] = []) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const getNestedValue = (obj: any, path: string): any => { + return path.split('.').reduce((acc, key) => { + const arrayMatch = key.match(/(\w+)\[(\d+)\]/); + if (arrayMatch) { + const [, arrayKey, index] = arrayMatch; + return acc && acc[arrayKey] && acc[arrayKey][Number(index)]; + } + return acc && acc[key]; + }, obj); + }; + return items.filter((item) => { + let keep = true; + filters.forEach((filter) => { + const value = getNestedValue(item, filter.key as string); + const comp = filter.value as string; + switch (filter.comparator) { + case FilterComparator.Includes: + keep = keep && `${value}`.toLowerCase().includes(comp.toLowerCase()); + break; + case FilterComparator.StartsWith: + keep = + keep && `${value}`.toLowerCase().startsWith(comp.toLowerCase()); + break; + case FilterComparator.EndsWith: + keep = keep && `${value}`.toLowerCase().endsWith(comp.toLowerCase()); + break; + case FilterComparator.IsEqual: + keep = keep && `${value}`.toLowerCase() === comp.toLowerCase(); + break; + case FilterComparator.IsDifferent: + keep = keep && `${value}`.toLowerCase() !== comp.toLowerCase(); + break; + case FilterComparator.IsLower: + keep = keep && Number(value) < Number(comp); + break; + case FilterComparator.IsHigher: + keep = keep && Number(value) > Number(comp); + break; + case FilterComparator.IsBefore: + keep = keep && new Date(`${value}`) < new Date(comp); + break; + case FilterComparator.IsAfter: + keep = keep && new Date(`${value}`) > new Date(comp); + break; + case FilterComparator.IsIn: + keep = + keep && !!(filter.value as string[]).find((i) => i === `${value}`); + break; + default: + break; + } + }); + return keep; + }); +} diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/Services.page.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/Services.page.tsx index b36c025d9c81..428e1b694830 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/Services.page.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/Services.page.tsx @@ -48,22 +48,6 @@ const Services = () => { } /> - diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/Services.spec.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/Services.spec.tsx index a7923ce4cd0c..4d342b1cedac 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/Services.spec.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/Services.spec.tsx @@ -13,6 +13,7 @@ import * as serviceApi from '@/data/api/database/service.api'; import { RouterWithQueryClientWrapper } from '@/__tests__/helpers/wrappers/RouterWithQueryClientWrapper'; import { mockedUser } from '@/__tests__/helpers/mocks/user'; import { mockedService } from '@/__tests__/helpers/mocks/services'; +import { apiErrorMock } from '@/__tests__/helpers/mocks/cdbError'; const mockedUsedNavigate = vi.fn(); describe('Services List page', () => { @@ -67,24 +68,12 @@ describe('Services List page', () => { }); it('should display services pages and skeleton', async () => { - render(, { wrapper: RouterWithQueryClientWrapper }); - expect( - screen.getByTestId('service-list-table-skeleton'), - ).toBeInTheDocument(); - await waitFor(() => { - expect( - screen.getByTestId('services-guides-container'), - ).toBeInTheDocument(); + vi.mocked(serviceApi.editService).mockImplementationOnce(() => { + throw apiErrorMock; }); - }); - - it('should display services pages and skeleton', async () => { - vi.mocked(serviceApi.getServices).mockImplementationOnce(() => null); render(, { wrapper: RouterWithQueryClientWrapper }); await waitFor(() => { - expect( - screen.getByTestId('services-guides-container'), - ).toBeInTheDocument(); + expect(screen.getByTestId('datatable.skeleton')).toBeInTheDocument(); }); }); diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/backups/Backups.page.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/backups/Backups.page.tsx index 3db338816475..4a712c5a21f6 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/backups/Backups.page.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/backups/Backups.page.tsx @@ -6,7 +6,6 @@ import Link from '@/components/links/Link.component'; import * as database from '@/types/cloud/project/database'; import { useServiceData } from '../Service.context'; import { getColumns } from './_components/BackupsTableColumns.component'; -import { DataTable } from '@/components/ui/data-table'; import { POLLING } from '@/configuration/polling.constants'; import { Button } from '@/components/ui/button'; import { Table, TableBody, TableCell, TableRow } from '@/components/ui/table'; @@ -14,6 +13,7 @@ import Guides from '@/components/guides/Guides.component'; import { useUserActivityContext } from '@/contexts/UserActivityContext'; import { GuideSections } from '@/types/guide'; import { useGetBackups } from '@/hooks/api/database/backup/useGetBackups.hook'; +import DataTable from '@/components/data-table'; export interface BackupWithExpiricyDate extends database.Backup { expiricyDate: Date; @@ -110,7 +110,7 @@ const Backups = () => { {backupsQuery.isSuccess ? ( - ({ ...backup, diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/backups/_components/BackupsTableColumns.component.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/backups/_components/BackupsTableColumns.component.tsx index a5a0b0c888d0..9c20d035c4de 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/backups/_components/BackupsTableColumns.component.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/backups/_components/BackupsTableColumns.component.tsx @@ -2,8 +2,6 @@ import { ColumnDef } from '@tanstack/react-table'; import { useTranslation } from 'react-i18next'; import { MoreHorizontal } from 'lucide-react'; -import { SortableHeader } from '@/components/ui/data-table'; - import UserStatusBadge from '../../users/_components/UserStatusBadge.component'; import FormattedDate from '@/components/formatted-date/FormattedDate.component'; import { BackupWithExpiricyDate } from '../Backups.page'; @@ -16,6 +14,8 @@ import { import { Button } from '@/components/ui/button'; import { useServiceData } from '../../Service.context'; import * as database from '@/types/cloud/project/database'; +import DataTable from '@/components/data-table'; +import { MENU_COLUMN_ID } from '@/components/data-table/DataTable.component'; interface BackupsTableColumnsProps { onForkClick: (backup: BackupWithExpiricyDate) => void; @@ -33,7 +33,9 @@ export const getColumns = ({ const nameColumn: ColumnDef = { id: 'name', header: ({ column }) => ( - {t('tableHeadName')} + + {t('tableHeadName')} + ), accessorFn: (row) => row.description, }; @@ -47,9 +49,9 @@ export const getColumns = ({ id: 'Creation date', accessorFn: (row) => row.createdAt, header: ({ column }) => ( - + {t('tableHeadCreationDate')} - + ), cell: ({ row }) => ( row.expiricyDate, header: ({ column }) => ( - + {t('tableHeadNExpiryDate')} - + ), cell: ({ row }) => { return ( @@ -85,14 +87,16 @@ export const getColumns = ({ id: 'Status', accessorFn: (row) => row.status, header: ({ column }) => ( - {t('tableHeadStatus')} + + {t('tableHeadStatus')} + ), cell: ({ row }) => { return ; }, }; const actionsColumn: ColumnDef = { - id: 'actions', + id: MENU_COLUMN_ID, cell: ({ row }) => { return (
diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/databases/Database.page.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/databases/Database.page.tsx index f6e1ae6da4b9..02e24d813aa6 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/databases/Database.page.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/databases/Database.page.tsx @@ -5,7 +5,7 @@ import { Outlet, useNavigate } from 'react-router-dom'; import BreadcrumbItem from '@/components/breadcrumb/BreadcrumbItem.component'; import * as database from '@/types/cloud/project/database'; import { useServiceData } from '../Service.context'; -import { DataTable } from '@/components/ui/data-table'; +import DataTable from '@/components/data-table'; import { getColumns } from './_components/DatabasesTableColumns.component'; import { Button } from '@/components/ui/button'; import { useUserActivityContext } from '@/contexts/UserActivityContext'; @@ -61,7 +61,11 @@ const Databases = () => { )} {databasesQuery.isSuccess ? ( - + ) : (
diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/databases/_components/DatabasesTableColumns.component.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/databases/_components/DatabasesTableColumns.component.tsx index ce6a01dc0230..3d043e1983dd 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/databases/_components/DatabasesTableColumns.component.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/databases/_components/DatabasesTableColumns.component.tsx @@ -2,7 +2,6 @@ import { ColumnDef } from '@tanstack/react-table'; import { MoreHorizontal } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/button'; -import { SortableHeader } from '@/components/ui/data-table'; import { DropdownMenu, DropdownMenuContent, @@ -17,6 +16,8 @@ import { } from '@/components/ui/tooltip'; import * as database from '@/types/cloud/project/database'; import { useServiceData } from '../../Service.context'; +import { MENU_COLUMN_ID } from '@/components/data-table/DataTable.component'; +import DataTable from '@/components/data-table'; interface DatabasesTableColumnsProps { onDeleteClick: (db: database.service.Database) => void; @@ -30,12 +31,14 @@ export const getColumns = ({ onDeleteClick }: DatabasesTableColumnsProps) => { { id: 'name', header: ({ column }) => ( - {t('tableHeadName')} + + {t('tableHeadName')} + ), accessorFn: (row) => row.name, }, { - id: 'actions', + id: MENU_COLUMN_ID, cell: ({ row }) => { return (
diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/integrations/Integrations.page.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/integrations/Integrations.page.tsx index 1634a494b6df..b25ef7502703 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/integrations/Integrations.page.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/integrations/Integrations.page.tsx @@ -5,7 +5,7 @@ import { Outlet, useNavigate } from 'react-router-dom'; import BreadcrumbItem from '@/components/breadcrumb/BreadcrumbItem.component'; import * as database from '@/types/cloud/project/database'; import { useServiceData } from '../Service.context'; -import { DataTable } from '@/components/ui/data-table'; +import DataTable from '@/components/data-table'; import { Button } from '@/components/ui/button'; import { POLLING } from '@/configuration/polling.constants'; import { getColumns } from './_components/IntegrationListColumns.component'; @@ -87,7 +87,11 @@ const Integrations = () => { )} {!isLoading ? ( - + ) : (
diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/integrations/_components/IntegrationListColumns.component.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/integrations/_components/IntegrationListColumns.component.tsx index 46a93f440525..9acdf7c45519 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/integrations/_components/IntegrationListColumns.component.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/integrations/_components/IntegrationListColumns.component.tsx @@ -2,7 +2,6 @@ import { ColumnDef } from '@tanstack/react-table'; import { MoreHorizontal } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/button'; -import { SortableHeader } from '@/components/ui/data-table'; import { DropdownMenu, DropdownMenuContent, @@ -14,6 +13,8 @@ import { useServiceData } from '../../Service.context'; import UserStatusBadge from '../../users/_components/UserStatusBadge.component'; import { IntegrationWithServices } from '../Integrations.page'; import IntegrationServiceLink from './IntegrationServiceLink.component'; +import DataTable from '@/components/data-table'; +import { MENU_COLUMN_ID } from '@/components/data-table/DataTable.component'; interface IntegrationsTableColumnsProps { onDeleteClick: (db: database.service.Integration) => void; @@ -29,7 +30,9 @@ export const getColumns = ({ { id: 'source', header: ({ column }) => ( - {t('tableHeadSource')} + + {t('tableHeadSource')} + ), accessorFn: (row) => row.source.description, cell: ({ row }) => ( @@ -39,9 +42,9 @@ export const getColumns = ({ { id: 'destination', header: ({ column }) => ( - + {t('tableHeadDestination')} - + ), accessorFn: (row) => row.destination.description, cell: ({ row }) => ( @@ -51,20 +54,24 @@ export const getColumns = ({ { id: 'type', header: ({ column }) => ( - {t('tableHeadType')} + + {t('tableHeadType')} + ), accessorFn: (row) => row.type, }, { id: 'status', header: ({ column }) => ( - {t('tableHeadStatus')} + + {t('tableHeadStatus')} + ), accessorFn: (row) => row.status, cell: ({ row }) => , }, { - id: 'actions', + id: MENU_COLUMN_ID, cell: ({ row }) => { return (
diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/namespaces/Namespace.page.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/namespaces/Namespace.page.tsx index 670484429f2e..f487364912c7 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/namespaces/Namespace.page.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/namespaces/Namespace.page.tsx @@ -5,7 +5,6 @@ import { Plus } from 'lucide-react'; import { Outlet, useNavigate } from 'react-router-dom'; import * as database from '@/types/cloud/project/database'; import { Button } from '@/components/ui/button'; -import { DataTable } from '@/components/ui/data-table'; import { useUserActivityContext } from '@/contexts/UserActivityContext'; import { useServiceData } from '../Service.context'; @@ -14,6 +13,7 @@ import { getColumns } from './_components/NamespacesTableColumns.component'; import { NAMESPACES_CONFIG } from './_components/formNamespace/namespace.constants'; import BreadcrumbItem from '@/components/breadcrumb/BreadcrumbItem.component'; import { useGetNamespaces } from '@/hooks/api/database/namespace/useGetNamespaces.hook'; +import DataTable from '@/components/data-table'; export function breadcrumb() { return ( @@ -75,7 +75,7 @@ const Namespaces = () => { )} {namespacesQuery.isSuccess ? ( - void; @@ -40,23 +41,27 @@ export const getColumns = ({ { id: 'name', header: ({ column }) => ( - {t('tableHeadName')} + + {t('tableHeadName')} + ), accessorFn: (row) => row.name, }, { id: 'type', header: ({ column }) => ( - {t('tableHeadType')} + + {t('tableHeadType')} + ), accessorFn: (row) => row.type, }, { id: 'retention', header: ({ column }) => ( - + {t('tableHeadRetentionTime')} - + ), accessorFn: (row) => durationStringToHuman(row.retention.periodDuration, dateLocale), @@ -64,14 +69,14 @@ export const getColumns = ({ { id: 'resolution', header: ({ column }) => ( - + {t('tableHeadResolution')} - + ), accessorFn: (row) => durationStringToHuman(row.resolution, dateLocale), }, { - id: 'actions', + id: MENU_COLUMN_ID, cell: ({ row }) => { return (
diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/pools/Pools.page.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/pools/Pools.page.tsx index 99f27cbacedf..d399e351b521 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/pools/Pools.page.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/pools/Pools.page.tsx @@ -1,10 +1,10 @@ import { useTranslation } from 'react-i18next'; import { ColumnDef } from '@tanstack/react-table'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { Outlet, useNavigate } from 'react-router-dom'; import { Plus } from 'lucide-react'; import { Button } from '@/components/ui/button'; -import { DataTable } from '@/components/ui/data-table'; +import DataTable from '@/components/data-table'; import BreadcrumbItem from '@/components/breadcrumb/BreadcrumbItem.component'; import { useServiceData } from '../Service.context'; import { POLLING } from '@/configuration/polling.constants'; @@ -39,9 +39,6 @@ const Pools = () => { 'pci-databases-analytics/services/service/pools', ); const { projectId, service } = useServiceData(); - const [connectionPoolListWithData, setConnectionPoolListWithData] = useState< - ConnectionPoolWithData[] - >([]); const { isUserActive } = useUserActivityContext(); const connectionPoolsQuery = useGetConnectionPools( projectId, @@ -63,7 +60,7 @@ const Pools = () => { refetchInterval: isUserActive && POLLING.USERS, }); - useEffect(() => { + const connectionPoolListWithData = useMemo(() => { if ( !( connectionPoolsQuery.isSuccess && @@ -71,18 +68,23 @@ const Pools = () => { databasesQuery.isSuccess ) ) - return; - const cpListWithData: ConnectionPoolWithData[] = connectionPoolsQuery.data.map( - (cp) => ({ - ...cp, - user: cp.userId - ? usersQuery.data.find((user) => user.id === cp.userId) - : null, - database: databasesQuery.data.find((db) => db.id === cp.databaseId), - }), - ); - setConnectionPoolListWithData(cpListWithData); - }, [connectionPoolsQuery.data, usersQuery.data, databasesQuery.data]); + return []; + + return connectionPoolsQuery.data.map((cp) => ({ + ...cp, + user: cp.userId + ? usersQuery.data.find((user) => user.id === cp.userId) + : null, + database: databasesQuery.data.find((db) => db.id === cp.databaseId), + })); + }, [ + connectionPoolsQuery.isSuccess, + connectionPoolsQuery.data, + usersQuery.isSuccess, + usersQuery.data, + databasesQuery.isSuccess, + databasesQuery.data, + ]); const columns: ColumnDef[] = getColumns({ onGetInformationClick: (pool: ConnectionPoolWithData) => @@ -117,7 +119,7 @@ const Pools = () => { )} {connectionPoolsQuery.isSuccess && connectionPoolListWithData ? ( - { })); vi.mock('@/data/api/database/user.api', () => ({ - getUsers: vi.fn(() => [mockedUser]), + getUsers: vi.fn(() => [mockedDatabaseUser]), })); vi.mock('@/data/api/database/certificate.api', () => ({ @@ -107,6 +109,9 @@ describe('Connection pool page', () => { }); it('renders and shows skeletons while loading', async () => { + vi.mocked(poolsApi.getConnectionPools).mockImplementationOnce(() => { + throw apiErrorMock; + }); render(, { wrapper: RouterWithQueryClientWrapper }); await waitFor(() => { expect( diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/pools/_components/PoolsTableColumns.component.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/pools/_components/PoolsTableColumns.component.tsx index 132d6487ea16..657d92bb6b74 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/pools/_components/PoolsTableColumns.component.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/pools/_components/PoolsTableColumns.component.tsx @@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'; import { ColumnDef } from '@tanstack/react-table'; import { MoreHorizontal } from 'lucide-react'; -import { SortableHeader } from '@/components/ui/data-table'; +import DataTable from '@/components/data-table'; import { Tooltip, TooltipProvider, @@ -19,6 +19,7 @@ import { Button } from '@/components/ui/button'; import { ConnectionPoolWithData } from '../Pools.page'; import { useServiceData } from '../../Service.context'; import * as database from '@/types/cloud/project/database'; +import { MENU_COLUMN_ID } from '@/components/data-table/DataTable.component'; interface ConnectionPoolsTableColumnsProps { onGetInformationClick: (connectionPool: ConnectionPoolWithData) => void; @@ -38,44 +39,50 @@ export const getColumns = ({ { id: 'name', header: ({ column }) => ( - {t('tableHeadName')} + + {t('tableHeadName')} + ), accessorFn: (row) => row.name, }, { id: 'database', header: ({ column }) => ( - + {t('tableHeadDatabase')} - + ), accessorFn: (row) => row.database.name, }, { id: 'mode', header: ({ column }) => ( - {t('tableHeadMode')} + + {t('tableHeadMode')} + ), accessorFn: (row) => row.mode, }, { id: 'size', header: ({ column }) => ( - {t('tableHeadSize')} + + {t('tableHeadSize')} + ), accessorFn: (row) => row.size, }, { id: 'username', header: ({ column }) => ( - + {t('tableHeadUsername')} - + ), accessorFn: (row) => (row.user ? row.user.username : ''), }, { - id: 'actions', + id: MENU_COLUMN_ID, cell: ({ row }) => { return (
diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/queries/_components/CurrentQueries.component.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/queries/_components/CurrentQueries.component.tsx index 63e809ee65a8..3d627b4b4179 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/queries/_components/CurrentQueries.component.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/queries/_components/CurrentQueries.component.tsx @@ -2,7 +2,7 @@ import { ColumnDef } from '@tanstack/react-table'; import { useTranslation } from 'react-i18next'; import { useState } from 'react'; import { useServiceData } from '../../Service.context'; -import { DataTable } from '@/components/ui/data-table'; +import DataTable from '@/components/data-table'; import * as database from '@/types/cloud/project/database'; import { getColumns } from './CurrentQueriesTableColumns.component'; import { useToast } from '@/components/ui/use-toast'; @@ -128,7 +128,11 @@ const CurrentQueries = () => {
{currentQueriesQuery.isSuccess ? ( - + ) : (
diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/queries/_components/CurrentQueriesTableColumns.component.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/queries/_components/CurrentQueriesTableColumns.component.tsx index 34eb6af2e968..0a8902cb42e0 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/queries/_components/CurrentQueriesTableColumns.component.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/queries/_components/CurrentQueriesTableColumns.component.tsx @@ -1,7 +1,7 @@ import { useTranslation } from 'react-i18next'; import { MoreHorizontal } from 'lucide-react'; import { ColumnDef } from '@tanstack/react-table'; -import { SortableHeader } from '@/components/ui/data-table'; +import DataTable from '@/components/data-table'; import { ExpandableSqlQuery } from './ExpandableSqlQuery.component'; import { DropdownMenu, @@ -12,6 +12,7 @@ import { import { Button } from '@/components/ui/button'; import * as database from '@/types/cloud/project/database'; import { useServiceData } from '../../Service.context'; +import { MENU_COLUMN_ID } from '@/components/data-table/DataTable.component'; interface CurrentQueriesListColumnsProps { onCancelQueryClicked: (query: database.service.currentqueries.Query) => void; @@ -27,9 +28,9 @@ export const getColumns = ({ { id: 'query', header: ({ column }) => ( - + {t('tableCurrentQueriesHeadName')} - + ), accessorFn: (row) => row.query, cell: ({ row }) => , @@ -37,50 +38,50 @@ export const getColumns = ({ { id: 'pid', header: ({ column }) => ( - + {t('tableCurrentQueriesHeadPid')} - + ), accessorFn: (row) => row.pid, }, { id: 'queryDuration', header: ({ column }) => ( - + {t('tableCurrentQueriesHeadQueryDuration')} - + ), accessorFn: (row) => row.queryDuration, }, { id: 'databaseName', header: ({ column }) => ( - + {t('tableCurrentQueriesHeadDatabaseName')} - + ), accessorFn: (row) => row.databaseName, }, { id: 'clientIp', header: ({ column }) => ( - + {t('tableCurrentQueriesHeadClientIp')} - + ), accessorFn: (row) => row.clientIp, }, { id: 'applicationName', header: ({ column }) => ( - + {t('tableCurrentQueriesHeadApplicationName')} - + ), accessorFn: (row) => row.applicationName, }, { - id: 'actions', + id: MENU_COLUMN_ID, cell: ({ row }) => (
diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/queries/_components/QueryStatistics.component.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/queries/_components/QueryStatistics.component.tsx index c1bd4fa9ff34..fd49663f01ad 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/queries/_components/QueryStatistics.component.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/queries/_components/QueryStatistics.component.tsx @@ -2,7 +2,7 @@ import { ColumnDef } from '@tanstack/react-table'; import { useTranslation } from 'react-i18next'; import { RotateCcw } from 'lucide-react'; import { useServiceData } from '../../Service.context'; -import { DataTable } from '@/components/ui/data-table'; +import DataTable from '@/components/data-table'; import * as database from '@/types/cloud/project/database'; import { Button } from '@/components/ui/button'; import { useToast } from '@/components/ui/use-toast'; @@ -72,7 +72,7 @@ const QueryStatistics = () => { )} {queryStatisticsQuery.isSuccess ? ( - { { id: 'query', header: ({ column }) => ( - + {t('tableQueryStatisticsHeadName')} - + ), accessorFn: (row) => row.query, cell: ({ row }) => , @@ -22,36 +22,36 @@ export const getColumns = () => { { id: 'rows', header: ({ column }) => ( - + {t('tableQueryStatisticsHeadRows')} - + ), accessorFn: (row) => row.rows, }, { id: 'calls', header: ({ column }) => ( - + {t('tableQueryStatisticsHeadCalls')} - + ), accessorFn: (row) => row.calls, }, { id: 'minTime', header: ({ column }) => ( - + {t('tableQueryStatisticsHeadMinTime')} - + ), accessorFn: (row) => row.minTime.toFixed(2), }, { id: 'maxTime', header: ({ column }) => ( - + {t('tableQueryStatisticsHeadMaxTime')} - + ), accessorFn: (row) => row.maxTime.toFixed(2), }, @@ -59,9 +59,9 @@ export const getColumns = () => { { id: 'meamTime', header: ({ column }) => ( - + {t('tableQueryStatisticsHeadMeanTime')} - + ), accessorFn: (row) => row.meanTime.toFixed(2), }, @@ -69,18 +69,18 @@ export const getColumns = () => { { id: 'stdDevTime', header: ({ column }) => ( - + {t('tableQueryStatisticsHeadStdDevTime')} - + ), accessorFn: (row) => row.stddevTime.toFixed(2), }, { id: 'totalTime', header: ({ column }) => ( - + {t('tableQueryStatisticsHeadTotalTime')} - + ), accessorFn: (row) => row.totalTime.toFixed(2), }, diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/users/Users.page.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/users/Users.page.tsx index 49ac4efa778a..3c9ad186d6f8 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/users/Users.page.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/users/Users.page.tsx @@ -9,9 +9,11 @@ import { GenericUser } from '@/data/api/database/user.api'; import * as database from '@/types/cloud/project/database'; import { getColumns } from './_components/UsersTableColumns.component'; import { Button } from '@/components/ui/button'; -import { DataTable } from '@/components/ui/data-table'; +import DataTable from '@/components/data-table'; import { useUserActivityContext } from '@/contexts/UserActivityContext'; import { POLLING } from '@/configuration/polling.constants'; +import { FilterCategories } from '@/lib/filters'; +import UserStatusBadge from './_components/UserStatusBadge.component'; export function breadcrumb() { return ( @@ -53,32 +55,64 @@ const Users = () => { }, }); + const filters = [ + { + id: 'username', + label: "Nom d'utilisateur", + comparators: FilterCategories.String, + }, + { + id: 'createdAt', + label: 'Date de création', + comparators: FilterCategories.Date, + }, + { + id: 'status', + label: 'Statut', + comparators: FilterCategories.Options, + options: Object.values(database.StatusEnum).map((value) => ({ + label: , + value, + })), + }, + ]; + return ( <>

{t('title')}

- {service.capabilities.users?.create && ( - - )} - {usersQuery.isSuccess ? ( - + + + {service.capabilities.users?.create && ( + + + + )} + + + + + + + ) : ( -
- -
+ )} diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/users/Users.spec.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/users/Users.spec.tsx index d781e57f513e..ad5d3e38a240 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/users/Users.spec.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/users/Users.spec.tsx @@ -98,7 +98,7 @@ describe('Users page', () => { throw apiErrorMock; }); render(, { wrapper: RouterWithQueryClientWrapper }); - expect(screen.getByTestId('users-table-skeleton')).toBeInTheDocument(); + expect(screen.getByTestId('datatable.skeleton')).toBeInTheDocument(); }); it('renders and shows users table', async () => { render(, { wrapper: RouterWithQueryClientWrapper }); @@ -115,7 +115,7 @@ describe('Users page', () => { ]); render(, { wrapper: RouterWithQueryClientWrapper }); await waitFor(() => { - expect(screen.getByText('mongoRole')).toBeInTheDocument(); + expect(screen.getByText('tableHeadRoles')).toBeInTheDocument(); }); }); it('renders redis columns', async () => { diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/users/_components/UsersTableColumns.component.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/users/_components/UsersTableColumns.component.tsx index 45da0a54d588..642ee8f863ac 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/users/_components/UsersTableColumns.component.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/[serviceId]/users/_components/UsersTableColumns.component.tsx @@ -3,9 +3,10 @@ import { useTranslation } from 'react-i18next'; import { Badge } from '@/components/ui/badge'; import FormattedDate from '@/components/formatted-date/FormattedDate.component'; import { GenericUser } from '@/data/api/database/user.api'; -import { SortableHeader } from '@/components/ui/data-table'; +import DataTable from '@/components/data-table'; import UserStatusBadge from './UserStatusBadge.component'; import UserActions from './UsersTableActions.component'; +import { MENU_COLUMN_ID } from '@/components/data-table/DataTable.component'; interface UserListColumnsProps { displayGroupCol: boolean; @@ -36,14 +37,18 @@ export const getColumns = ({ const userNameColumn: ColumnDef = { id: 'username', header: ({ column }) => ( - {t('tableHeadName')} + + {t('tableHeadName')} + ), accessorFn: (row) => row.username, }; const groupColumn: ColumnDef = { id: 'group', header: ({ column }) => ( - {t('tableHeadGroup')} + + {t('tableHeadGroup')} + ), accessorFn: (row) => ('group' in row ? row.group : ''), }; @@ -68,7 +73,9 @@ export const getColumns = ({ const keysColumn: ColumnDef = { id: 'keys', header: ({ column }) => ( - {t('tableHeadKeys')} + + {t('tableHeadKeys')} + ), accessorFn: (row) => ('keys' in row ? row.keys.length : ''), cell: ({ row }) => ( @@ -88,9 +95,9 @@ export const getColumns = ({ const categoriesColumn: ColumnDef = { id: 'categories', header: ({ column }) => ( - + {t('tableHeadCategories')} - + ), accessorFn: (row) => ('categories' in row ? row.categories.length : ''), cell: ({ row }) => ( @@ -110,7 +117,9 @@ export const getColumns = ({ const commandsColumn: ColumnDef = { id: 'commands', header: ({ column }) => ( - {t('tableHeadCommands')} + + {t('tableHeadCommands')} + ), accessorFn: (row) => ('commands' in row ? row.commands.length : ''), cell: ({ row }) => ( @@ -130,7 +139,9 @@ export const getColumns = ({ const channelsColumn: ColumnDef = { id: 'channels', header: ({ column }) => ( - {t('tableHeadChannels')} + + {t('tableHeadChannels')} + ), accessorFn: (row) => ('channels' in row ? row.channels.length : ''), cell: ({ row }) => ( @@ -151,9 +162,9 @@ export const getColumns = ({ id: 'Creation date', accessorFn: (row) => row.createdAt, header: ({ column }) => ( - + {t('tableHeadCreationDate')} - + ), cell: ({ row }) => ( @@ -163,14 +174,16 @@ export const getColumns = ({ id: 'Status', accessorFn: (row) => row.status, header: ({ column }) => ( - {t('tableHeadStatus')} + + {t('tableHeadStatus')} + ), cell: ({ row }) => { return ; }, }; const actionsColumn: ColumnDef = { - id: 'actions', + id: MENU_COLUMN_ID, cell: ({ row }) => ( void; @@ -37,9 +38,11 @@ export const getColumns = ({ { id: 'description/id', header: ({ column }) => ( - {t('tableHeaderName')} + + {t('tableHeaderName')} + ), - accessorFn: (row) => row.description, + accessorFn: (row) => `${row.description}-${row.id}`, cell: ({ row }) => { const { id, description, status, engine, nodes } = row.original; return ( @@ -78,9 +81,9 @@ export const getColumns = ({ id: 'Engine', accessorFn: (row) => row.engine, header: ({ column }) => ( - + {t('tableHeaderEngine')} - + ), cell: ({ row }) => { const { engine, version } = row.original; @@ -99,7 +102,9 @@ export const getColumns = ({ id: 'Plan', accessorFn: (row) => row.plan, header: ({ column }) => ( - {t('tableHeaderPlan')} + + {t('tableHeaderPlan')} + ), cell: ({ row }) => { const { plan } = row.original; @@ -110,9 +115,9 @@ export const getColumns = ({ id: 'Flavor', accessorFn: (row) => row.flavor, header: ({ column }) => ( - + {t('tableHeaderFlavor')} - + ), cell: ({ row }) => { const { flavor } = row.original; @@ -123,16 +128,16 @@ export const getColumns = ({ id: 'Storage', accessorFn: (row) => row.storage?.size.value ?? 0, header: ({ column }) => ( - + {t('tableHeaderStorage')} - + ), cell: ({ row }) => { const service = row.original; return ( {service.storage && service.storage.size.value > 0 - ? `${service.storage.size.value} ${service.storage.size.unit}` + ? `${formatStorage(service.storage.size)}` : '-'} ); @@ -142,9 +147,9 @@ export const getColumns = ({ id: 'Region', accessorFn: (row) => row.nodes[0].region, header: ({ column }) => ( - + {t('tableHeaderLocation')} - + ), cell: ({ row }) => ( {tRegions(`region_${row.original.nodes[0].region}`)} @@ -154,7 +159,9 @@ export const getColumns = ({ id: 'Nodes', accessorFn: (row) => row.nodes.length, header: ({ column }) => ( - {t('tableHeaderNodes')} + + {t('tableHeaderNodes')} + ), cell: ({ row }) => { const service = row.original; @@ -200,9 +207,9 @@ export const getColumns = ({ id: 'Creation date', accessorFn: (row) => row.createdAt, header: ({ column }) => ( - + {t('tableHeaderCreationDate')} - + ), cell: ({ row }) => ( @@ -214,16 +221,17 @@ export const getColumns = ({ id: 'Status', accessorFn: (row) => row.status, header: ({ column }) => ( - + {t('tableHeaderStatus')} - + ), cell: ({ row }) => { return ; }, }, { - id: 'actions', + id: MENU_COLUMN_ID, + enableGlobalFilter: false, cell: ({ row }) => { const service = row.original; @@ -243,7 +251,6 @@ export const getColumns = ({ data-testid="services-action-content" align="end" > - Actions navigate(`./${row.original.id}`)} diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/_components/ServiceListFilters.component.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/_components/ServiceListFilters.component.tsx new file mode 100644 index 000000000000..34104792f4a9 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/_components/ServiceListFilters.component.tsx @@ -0,0 +1,74 @@ +import { useTranslation } from 'react-i18next'; +import * as database from '@/types/cloud/project/database'; +import { humanizeEngine } from '@/lib/engineNameHelper'; +import { FilterCategories } from '@/lib/filters'; +import ServiceStatusBadge from './ServiceStatusBadge.component'; +import { + FLAVORS_OPTIONS, + PLANS_OPTIONS, + REGIONS_OPTIONS, +} from './ServiceListFilters.constants'; + +export const getFilters = () => { + const { t } = useTranslation('pci-databases-analytics/services'); + const { t: tRegions } = useTranslation('regions'); + return [ + { + id: 'description', + label: t('tableHeaderName'), + comparators: FilterCategories.String, + }, + { + id: 'id', + label: 'ID', + comparators: FilterCategories.String, + }, + { + id: 'engine', + label: t('tableHeaderEngine'), + comparators: FilterCategories.Options, + options: Object.values(database.EngineEnum).map((value) => ({ + label: humanizeEngine(value), + value, + })), + }, + { + id: 'plan', + label: t('tableHeaderPlan'), + comparators: FilterCategories.Options, + options: PLANS_OPTIONS.map((plan) => ({ label: plan, value: plan })), + }, + { + id: 'nodes[0].region', + label: t('tableHeaderLocation'), + comparators: FilterCategories.Options, + options: REGIONS_OPTIONS.map((region) => ({ + label: tRegions(`region_${region}`), + value: region, + })), + }, + { + id: 'flavor', + label: t('tableHeaderFlavor'), + comparators: FilterCategories.Options, + options: FLAVORS_OPTIONS.map((flavor) => ({ + label: {flavor}, + value: flavor, + })), + }, + { + id: 'createdAt', + label: t('tableHeaderEngine'), + comparators: FilterCategories.Date, + }, + { + id: 'status', + label: t('tableHeaderStatus'), + comparators: FilterCategories.Options, + options: Object.values(database.StatusEnum).map((value) => ({ + label: , + value, + })), + }, + ]; +}; diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/_components/ServiceListFilters.constants.ts b/packages/manager/apps/pci-databases-analytics/src/pages/services/_components/ServiceListFilters.constants.ts new file mode 100644 index 000000000000..de3c3aed7442 --- /dev/null +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/_components/ServiceListFilters.constants.ts @@ -0,0 +1,27 @@ +export const PLANS_OPTIONS = [ + 'Discovery', + 'Production', + 'Advanced', + 'Essential', + 'Business', + 'Enterprise', +]; + +export const REGIONS_OPTIONS = ['BHS', 'DE', 'GRA', 'SBG', 'UK', 'WAW']; + +export const FLAVORS_OPTIONS = [ + 'db2-free', + 'db2-2', + 'db2-4', + 'db2-7', + 'db2-15', + 'db2-30', + 'db2-60', + 'db2-120', + 'db1-4', + 'db1-7', + 'db1-15', + 'db1-30', + 'db1-60', + 'db1-120', +]; diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/_components/ServiceListTable.component.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/_components/ServiceListTable.component.tsx index 0840de2ea2e4..6e8fef8d220b 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/_components/ServiceListTable.component.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/_components/ServiceListTable.component.tsx @@ -1,18 +1,24 @@ import { useNavigate } from 'react-router-dom'; import { ColumnDef } from '@tanstack/react-table'; -import { DataTable } from '@/components/ui/data-table'; +import { Plus } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; import * as database from '@/types/cloud/project/database'; import { Skeleton } from '@/components/ui/skeleton'; import { getColumns } from './ServiceListColumns.component'; import { useTrackAction } from '@/hooks/useTracking'; import { TRACKING } from '@/configuration/tracking.constants'; +import { Button } from '@/components/ui/button'; +import { getFilters } from './ServiceListFilters.component'; +import DataTable from '@/components/data-table'; interface ServicesListProps { services: database.Service[]; } export default function ServicesList({ services }: ServicesListProps) { + const { t } = useTranslation('pci-databases-analytics/services'); const track = useTrackAction(); + const navigate = useNavigate(); const columns: ColumnDef[] = getColumns({ @@ -30,16 +36,35 @@ export default function ServicesList({ services }: ServicesListProps) { navigate(`./delete/${service.id}`); }, }); + const servicesFilters = getFilters(); return ( - <> - - + + + + + + + + + + + + ); }