apps/nextjs-app/src/features/app/dashboard/DashboardGrid.tsx
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import type { IDashboardLayout } from '@teable/openapi';
import { getDashboard, updateLayoutDashboard } from '@teable/openapi';
import { ReactQueryKeys } from '@teable/sdk/config';
import { useBaseId, useBasePermission } from '@teable/sdk/hooks';
import { cn } from '@teable/ui-lib/shadcn';
import { useTranslation } from 'next-i18next';
import { useState } from 'react';
import { Responsive, WidthProvider } from 'react-grid-layout';
import { dashboardConfig } from '@/features/i18n/dashboard.config';
import { PluginItem } from './components/PluginItem';
import { useIsExpandPlugin } from './hooks/useIsExpandPlugin';
const ResponsiveGridLayout = WidthProvider(Responsive);
export const DashboardGrid = (props: { dashboardId: string }) => {
const { dashboardId } = props;
const baseId = useBaseId()!;
const queryClient = useQueryClient();
const isExpandPlugin = useIsExpandPlugin();
const { t } = useTranslation(dashboardConfig.i18nNamespaces);
const [isDragging, setIsDragging] = useState(false);
const basePermissions = useBasePermission();
const canMange = basePermissions?.['base|update'];
const { data: dashboardData } = useQuery({
queryKey: ReactQueryKeys.getDashboard(dashboardId),
queryFn: () => getDashboard(baseId, dashboardId).then((res) => res.data),
});
const { mutate: updateLayoutDashboardMutate } = useMutation({
mutationFn: (layout: IDashboardLayout) => updateLayoutDashboard(baseId, dashboardId, layout),
onSuccess: () => {
queryClient.invalidateQueries(ReactQueryKeys.getDashboard(dashboardId));
},
});
const layout = dashboardData?.layout ?? [];
const pluginMap = dashboardData?.pluginMap ?? {};
const onLayoutChange = (layout: ReactGridLayout.Layout[]) => {
updateLayoutDashboardMutate(
layout.map(({ i, x, y, w, h }) => ({ pluginInstallId: i, x, y, w, h }))
);
};
return (
<ResponsiveGridLayout
className="w-full"
layouts={{
md: layout.map(({ pluginInstallId, x, y, w, h }) => ({
i: pluginInstallId,
x,
y,
w,
h,
})),
}}
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
rowHeight={80}
margin={[16, 16]}
containerPadding={[16, 16]}
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }}
draggableHandle=".dashboard-draggable-handle"
onResize={() => setIsDragging(true)}
onResizeStop={(layout) => {
setIsDragging(false);
onLayoutChange(layout);
}}
onDrag={() => setIsDragging(true)}
onDragStop={(layout) => {
setIsDragging(false);
onLayoutChange(layout);
}}
isResizable={canMange}
isDraggable={canMange}
>
{layout.map(({ pluginInstallId, x, y, w, h }) => (
<div
key={pluginInstallId}
data-grid={{ x, y, w, h }}
className={cn({
'!transform-none !transition-none': isExpandPlugin(pluginInstallId),
})}
>
{pluginMap[pluginInstallId] ? (
<PluginItem
dragging={isDragging}
dashboardId={dashboardId}
name={pluginMap[pluginInstallId].name}
pluginId={pluginMap[pluginInstallId].id}
pluginUrl={pluginMap[pluginInstallId].url}
pluginInstallId={pluginMap[pluginInstallId].pluginInstallId}
/>
) : (
<div>{t('common:pluginCenter.pluginNotFound')}</div>
)}
</div>
))}
</ResponsiveGridLayout>
);
};