Skip to content

Commit

Permalink
Allow dialog on multiple rows
Browse files Browse the repository at this point in the history
  • Loading branch information
cregourd committed Sep 20, 2024
1 parent 9f58ac8 commit 8653fe1
Show file tree
Hide file tree
Showing 11 changed files with 56 additions and 42 deletions.
26 changes: 14 additions & 12 deletions apps/example/components/UserDetailsDialogContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ type Props = ClientActionDialogContentProps<"User">;

const UserDetailsDialog = ({ data, onClose }: Props) => {
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<h2 className="text-nextadmin-content-default dark:text-dark-nextadmin-content-default text-2xl font-semibold">
{data?.email as string}
</h2>
<p className="text-nextadmin-content-subtle dark:text-dark-nextadmin-content-subtle">
{data?.name as string}
</p>
<p className="text-nextadmin-content-subtle dark:text-dark-nextadmin-content-subtle">
{data?.role as string}
</p>
</div>
<div className="flex flex-col gap-8">
{data?.map((user) => (
<div className="flex flex-col gap-2" key={user.id}>
<h2 className="text-nextadmin-content-default dark:text-dark-nextadmin-content-default text-2xl font-semibold">
{user.email as string}
</h2>
<p className="text-nextadmin-content-subtle dark:text-dark-nextadmin-content-subtle">
{user.name as string}
</p>
<p className="text-nextadmin-content-subtle dark:text-dark-nextadmin-content-subtle">
{user.role as string}
</p>
</div>
))}
<div className="flex">
<Button variant="default" onClick={onClose}>
Close
Expand Down
14 changes: 11 additions & 3 deletions packages/next-admin/src/appHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const createHandler = <P extends string = "nextadmin">({
}

router
.get(`${apiBasePath}/:model/:id/raw`, async (req, ctx) => {
.get(`${apiBasePath}/:model/raw`, async (req, ctx) => {
const resource = getResourceFromParams(ctx.params[paramKey], resources);

if (!resource) {
Expand All @@ -52,8 +52,16 @@ export const createHandler = <P extends string = "nextadmin">({
);
}

const id = formatId(resource, ctx.params[paramKey].at(-2)!);
const data = await getRawData({ prisma, resource, resourceId: id });
const ids = req.nextUrl.searchParams
.get("ids")
?.split(",")
.map((id) => formatId(resource, id));

if (!ids) {
return NextResponse.json({ error: "No ids provided" }, { status: 400 });
}

const data = await getRawData({ prisma, resource, resourceIds: ids });

return NextResponse.json(data);
})
Expand Down
5 changes: 2 additions & 3 deletions packages/next-admin/src/components/ActionDropdownItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ type Props = {
action: ModelAction<ModelName> | Omit<ModelAction<ModelName>, "action">;
resource: ModelName;
resourceIds: string[] | number[];
data?: any;
};

const ActionDropdownItem = ({ action, resource, resourceIds, data }: Props) => {
const ActionDropdownItem = ({ action, resource, resourceIds }: Props) => {
const { t } = useI18n();
const { runAction } = useAction(resource, resourceIds);
const { open: openActionDialog } = useClientActionDialog();
Expand All @@ -38,7 +37,7 @@ const ActionDropdownItem = ({ action, resource, resourceIds, data }: Props) => {
openActionDialog({
action: action,
resource,
resourceId: resourceIds[0],
resourceIds,
});
} else {
runAction(action);
Expand Down
18 changes: 10 additions & 8 deletions packages/next-admin/src/components/ClientActionDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {

type Props<M extends ModelName> = {
resource: M;
resourceId: string | number;
resourceIds: Array<string | number>;
onClose: () => void;
action: ClientAction<M> & {
title: string;
Expand All @@ -25,23 +25,25 @@ type Props<M extends ModelName> = {

const ClientActionDialog = <M extends ModelName>({
resource,
resourceId,
resourceIds,
onClose,
action,
}: Props<M>) => {
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState<Model<M> | null>(null);
const [data, setData] = useState<Array<Model<M>> | null>(null);
const { apiBasePath } = useConfig();

useEffect(() => {
setIsLoading(true);
fetch(`${apiBasePath}/${slugify(resource)}/${resourceId}/raw`)
fetch(
`${apiBasePath}/${slugify(resource)}/raw?ids=${resourceIds.join(",")}`
)
.then((res) => res.json())
.then(setData)
.finally(() => {
setIsLoading(false);
});
}, [resource, resourceId]);
}, [resource, resourceIds]);

Check warning on line 46 in packages/next-admin/src/components/ClientActionDialog.tsx

View workflow job for this annotation

GitHub Actions / start

React Hook useEffect has a missing dependency: 'apiBasePath'. Either include it or remove the dependency array

return (
<DialogRoot
Expand Down Expand Up @@ -77,7 +79,7 @@ const ClientActionDialog = <M extends ModelName>({
>
<DialogContent
className={clsx(
"max-w-xl outline-none md:left-[50%] md:top-[50%]",
"max-h-[90vh] max-w-screen-md max-w-xl overflow-y-auto outline-none md:left-[50%] md:top-[50%]",
action?.className
)}
forceMount
Expand All @@ -90,8 +92,8 @@ const ClientActionDialog = <M extends ModelName>({
{data &&
cloneElement(action.component, {
data: data,
resource: resource,
resourceId: resourceId,
resource,
resourceIds,
onClose,
})}
</DialogContent>
Expand Down
1 change: 0 additions & 1 deletion packages/next-admin/src/components/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,6 @@ function List({
action={action}
resourceIds={[row.original[idProperty].value as string]}
resource={resource}
data={row.original}
/>
);
})}
Expand Down
6 changes: 2 additions & 4 deletions packages/next-admin/src/components/ListHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export default function ListHeader({
{
id: SPECIFIC_IDS_TO_RUN_ACTION.DELETE,
title: t("actions.delete.label"),
icon: "TrashIcon",
style: "destructive",
action: async () => {
await onDelete();
Expand Down Expand Up @@ -139,10 +140,7 @@ export default function ListHeader({
<AdvancedSearchButton resource={resource} schema={schema} />
{Boolean(selectedRowsCount) && !!actions.length && (
<ActionsDropdown
// We don't want client actions in the list header
actions={actions.filter(
(action) => !("type" in action && action.type === "dialog")
)}
actions={actions}
resource={resource}
selectedIds={getSelectedRowsIds()}
selectedCount={selectedRowsCount}
Expand Down
2 changes: 1 addition & 1 deletion packages/next-admin/src/components/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export default function Menu({

const getItemProps = (model: ModelName) => {
return {
name: t(`model.${model}.plural`, {}, resourcesTitles?.[model] || model),
name: t(`model.${model}.plural`, {}, resourcesTitles?.[model] ?? model),
href: `${basePath}/${slugify(model)}`,
current: model === currentResource,
icon: resourcesIcons?.[model],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ActionStyle, ClientAction, ModelName } from "../types";

type ClientActionDialogParams<M extends ModelName> = {
resource: M;
resourceId: string | number;
resourceIds: Array<string | number>;
action: ClientAction<M> & {
title: string;
id: string;
Expand Down
12 changes: 9 additions & 3 deletions packages/next-admin/src/pageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const createHandler = <P extends string = "nextadmin">({
}

router
.get(`${apiBasePath}/:model/:id/raw`, async (req, res) => {
.get(`${apiBasePath}/:model/raw`, async (req, res) => {
const resource = getResourceFromParams(
[req.query[paramKey]![0]],
resources
Expand All @@ -78,8 +78,14 @@ export const createHandler = <P extends string = "nextadmin">({
return res.status(404).json({ error: "Resource not found" });
}

const id = formatId(resource, req.query[paramKey]!.at(-2)!);
const data = await getRawData({ prisma, resource, resourceId: id });
let ids: any = req.query.ids;
if (Array.isArray(ids)) {
ids = ids.map((id) => formatId(resource, id));
} else {
ids = ids?.split(",").map((id: string) => formatId(resource, id));
}

const data = await getRawData({ prisma, resource, resourceIds: ids });

return res.json(data);
})
Expand Down
4 changes: 2 additions & 2 deletions packages/next-admin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1070,7 +1070,7 @@ export type FormProps = {

export type ClientActionDialogContentProps<T extends ModelName> = Partial<{
resource: ModelName;
resourceId: string | number;
data: Model<T>;
resourceIds: Array<string | number>;
data: Array<Model<T>>;
onClose?: () => void;
}>;
8 changes: 4 additions & 4 deletions packages/next-admin/src/utils/prisma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -670,11 +670,11 @@ export const getDataItem = async <M extends ModelName>({
export const getRawData = async ({
prisma,
resource,
resourceId,
resourceIds,
}: {
prisma: PrismaClient;
resource: ModelName;
resourceId: string | number;
resourceIds: Array<string | number>;
}) => {
const modelDMMF = getPrismaModelForResource(resource);

Expand All @@ -689,8 +689,8 @@ export const getRawData = async ({
);

// @ts-expect-error
const data = await prisma[resource].findUnique({
where: { id: resourceId },
const data = await prisma[resource].findMany({
where: { id: { in: resourceIds } },
include,
});

Expand Down

0 comments on commit 8653fe1

Please sign in to comment.