mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 04:46:05 +00:00
feat: add admin options for entities and app shell
Introduced `BkndAdminEntitiesOptions` and `BkndAdminAppShellOptions` for advanced customization of entity actions, headers, footers, and app shell user menu. Updated related components, hooks, and types for seamless integration with the new configuration options.
This commit is contained in:
@@ -18,6 +18,7 @@ import { EntityForm } from "ui/modules/data/components/EntityForm";
|
||||
import { EntityTable2 } from "ui/modules/data/components/EntityTable2";
|
||||
import { useEntityForm } from "ui/modules/data/hooks/useEntityForm";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useEntityAdminOptions } from "ui/options";
|
||||
|
||||
export function DataEntityUpdate({ params }) {
|
||||
return <DataEntityUpdateImpl params={params} key={params.entity} />;
|
||||
@@ -53,6 +54,7 @@ function DataEntityUpdateImpl({ params }) {
|
||||
},
|
||||
);
|
||||
|
||||
const options = useEntityAdminOptions(entity, "update", $q.data);
|
||||
const backHref = routes.data.entity.list(entity.name);
|
||||
const goBack = () => _goBack({ fallback: backHref });
|
||||
|
||||
@@ -125,6 +127,7 @@ function DataEntityUpdateImpl({ params }) {
|
||||
<Dropdown
|
||||
position="bottom-end"
|
||||
items={[
|
||||
...(options.actions?.context ?? []),
|
||||
{
|
||||
label: "Inspect",
|
||||
onClick: () => {
|
||||
@@ -156,6 +159,10 @@ function DataEntityUpdateImpl({ params }) {
|
||||
>
|
||||
<IconButton Icon={TbDots} />
|
||||
</Dropdown>
|
||||
{options.actions?.primary?.map(
|
||||
(button, key) =>
|
||||
button && <Button variant="primary" {...button} type="button" key={key} />,
|
||||
)}
|
||||
<Form.Subscribe
|
||||
selector={(state) => [state.canSubmit, state.isSubmitting]}
|
||||
children={([canSubmit, isSubmitting]) => (
|
||||
@@ -185,6 +192,7 @@ function DataEntityUpdateImpl({ params }) {
|
||||
</div>
|
||||
) : (
|
||||
<AppShell.Scrollable>
|
||||
{options.header}
|
||||
{error && (
|
||||
<div className="flex flex-row dark:bg-red-950 bg-red-100 p-4">
|
||||
<b className="mr-2">Update failed: </b> {error}
|
||||
@@ -200,6 +208,8 @@ function DataEntityUpdateImpl({ params }) {
|
||||
action="update"
|
||||
className="flex flex-grow flex-col gap-3 p-3"
|
||||
/>
|
||||
{options.footer}
|
||||
|
||||
{targetRelations.length > 0 ? (
|
||||
<EntityDetailRelations
|
||||
id={entityId}
|
||||
@@ -247,7 +257,8 @@ function EntityDetailRelations({
|
||||
return {
|
||||
as: "button",
|
||||
type: "button",
|
||||
label: ucFirst(other.entity.label),
|
||||
//label: ucFirst(other.entity.label),
|
||||
label: ucFirst(other.reference),
|
||||
onClick: () => handleClick(relation),
|
||||
active: selected?.other(entity).reference === other.reference,
|
||||
badge: relation.type(),
|
||||
|
||||
@@ -13,6 +13,10 @@ import { EntityForm } from "ui/modules/data/components/EntityForm";
|
||||
import { useEntityForm } from "ui/modules/data/hooks/useEntityForm";
|
||||
import { s } from "bknd/utils";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useEntityAdminOptions } from "ui/options";
|
||||
import { Dropdown } from "ui/components/overlay/Dropdown";
|
||||
import { TbDots } from "react-icons/tb";
|
||||
import { IconButton } from "ui/components/buttons/IconButton";
|
||||
|
||||
export function DataEntityCreate({ params }) {
|
||||
const { $data } = useBkndData();
|
||||
@@ -23,6 +27,7 @@ export function DataEntityCreate({ params }) {
|
||||
} else if (entity.type === "system") {
|
||||
return <Message.NotAllowed description={`Entity "${params.entity}" cannot be created.`} />;
|
||||
}
|
||||
const options = useEntityAdminOptions(entity, "create");
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
useBrowserTitle(["Data", entity.label, "Create"]);
|
||||
@@ -71,7 +76,16 @@ export function DataEntityCreate({ params }) {
|
||||
<AppShell.SectionHeader
|
||||
right={
|
||||
<>
|
||||
{options.actions?.context && (
|
||||
<Dropdown position="bottom-end" items={options.actions.context}>
|
||||
<IconButton Icon={TbDots} />
|
||||
</Dropdown>
|
||||
)}
|
||||
<Button onClick={goBack}>Cancel</Button>
|
||||
{options.actions?.primary?.map(
|
||||
(button, key) =>
|
||||
button && <Button {...button} type="button" key={key} variant="primary" />,
|
||||
)}
|
||||
<Form.Subscribe
|
||||
selector={(state) => [state.canSubmit, state.isSubmitting]}
|
||||
children={([canSubmit, isSubmitting]) => (
|
||||
@@ -96,6 +110,7 @@ export function DataEntityCreate({ params }) {
|
||||
/>
|
||||
</AppShell.SectionHeader>
|
||||
<AppShell.Scrollable key={entity.name}>
|
||||
{options.header}
|
||||
{error && (
|
||||
<div className="flex flex-row dark:bg-red-950 bg-red-100 p-4">
|
||||
<b className="mr-2">Create failed: </b> {error}
|
||||
@@ -110,6 +125,7 @@ export function DataEntityCreate({ params }) {
|
||||
action="create"
|
||||
className="flex flex-grow flex-col gap-3 p-3"
|
||||
/>
|
||||
{options.footer}
|
||||
</AppShell.Scrollable>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ import { useCreateUserModal } from "ui/modules/auth/hooks/use-create-user-modal"
|
||||
import { EntityTable2 } from "ui/modules/data/components/EntityTable2";
|
||||
import { s } from "bknd/utils";
|
||||
import { pick } from "core/utils/objects";
|
||||
import { useEntityAdminOptions } from "ui/options";
|
||||
|
||||
const searchSchema = s.partialObject({
|
||||
...pick(repoQuery.properties, ["select", "where", "sort"]),
|
||||
@@ -36,6 +37,7 @@ function DataEntityListImpl({ params }) {
|
||||
if (!entity) {
|
||||
return <Message.NotFound description={`Entity "${params.entity}" doesn't exist.`} />;
|
||||
}
|
||||
const options = useEntityAdminOptions(entity, "list");
|
||||
|
||||
useBrowserTitle(["Data", entity?.label ?? params.entity]);
|
||||
const [navigate] = useNavigate();
|
||||
@@ -100,6 +102,7 @@ function DataEntityListImpl({ params }) {
|
||||
<>
|
||||
<Dropdown
|
||||
items={[
|
||||
...(options.actions?.context ?? []),
|
||||
{
|
||||
label: "Settings",
|
||||
onClick: () => navigate(routes.data.schema.entity(entity.name)),
|
||||
@@ -120,6 +123,10 @@ function DataEntityListImpl({ params }) {
|
||||
>
|
||||
<IconButton Icon={TbDots} />
|
||||
</Dropdown>
|
||||
{options.actions?.primary?.map(
|
||||
(button, key) =>
|
||||
button && <Button variant="primary" {...button} type="button" key={key} />,
|
||||
)}
|
||||
<EntityCreateButton entity={entity} />
|
||||
</>
|
||||
}
|
||||
@@ -127,6 +134,7 @@ function DataEntityListImpl({ params }) {
|
||||
<AppShell.SectionHeaderTitle>{entity.label}</AppShell.SectionHeaderTitle>
|
||||
</AppShell.SectionHeader>
|
||||
<AppShell.Scrollable key={entity.name}>
|
||||
{options.header}
|
||||
<div className="flex flex-col flex-grow p-3 gap-3">
|
||||
{/*<div className="w-64">
|
||||
<SearchInput placeholder={`Filter ${entity.label}`} />
|
||||
@@ -134,7 +142,7 @@ function DataEntityListImpl({ params }) {
|
||||
|
||||
<div
|
||||
data-updating={isUpdating ? 1 : undefined}
|
||||
className="data-[updating]:opacity-50 transition-opacity pb-10"
|
||||
className="data-[updating]:opacity-50 transition-opacity"
|
||||
>
|
||||
<EntityTable2
|
||||
data={data ?? null}
|
||||
@@ -152,6 +160,7 @@ function DataEntityListImpl({ params }) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{options.footer}
|
||||
</AppShell.Scrollable>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -20,7 +20,12 @@ const TestRoutes = lazy(() => import("./test"));
|
||||
export function Routes({
|
||||
BkndWrapper,
|
||||
basePath = "",
|
||||
}: { BkndWrapper: ComponentType<{ children: ReactNode }>; basePath?: string }) {
|
||||
children,
|
||||
}: {
|
||||
BkndWrapper: ComponentType<{ children: ReactNode }>;
|
||||
basePath?: string;
|
||||
children?: ReactNode;
|
||||
}) {
|
||||
const { theme } = useTheme();
|
||||
const ctx = useBkndWindowContext();
|
||||
const actualBasePath = basePath || ctx.admin_basepath;
|
||||
@@ -44,6 +49,8 @@ export function Routes({
|
||||
</Suspense>
|
||||
</Route>
|
||||
|
||||
{children}
|
||||
|
||||
<Route path="/" component={RootEmpty} />
|
||||
<Route path="/data" nest>
|
||||
<Suspense fallback={null}>
|
||||
|
||||
Reference in New Issue
Block a user