updated admin to use swr hooks instead of react-query

This commit is contained in:
dswbx
2024-12-13 16:24:55 +01:00
parent 50c5adce0c
commit 8c91dff94d
20 changed files with 380 additions and 275 deletions

View File

@@ -2,12 +2,11 @@ import { ucFirst } from "core/utils";
import type { Entity, EntityData, EntityRelation } from "data";
import { Fragment, useState } from "react";
import { TbDots } from "react-icons/tb";
import { useClient } from "ui/client";
import { useClient, useEntityQuery } from "ui/client";
import { useBkndData } from "ui/client/schema/data/use-bknd-data";
import { Button } from "ui/components/buttons/Button";
import { IconButton } from "ui/components/buttons/IconButton";
import { Dropdown } from "ui/components/overlay/Dropdown";
import { useEntity } from "ui/container";
import { useBrowserTitle } from "ui/hooks/use-browser-title";
import * as AppShell from "ui/layouts/AppShell/AppShell";
import { Breadcrumbs2 } from "ui/layouts/AppShell/Breadcrumbs2";
@@ -25,22 +24,23 @@ export function DataEntityUpdate({ params }) {
const [navigate] = useNavigate();
useBrowserTitle(["Data", entity.label, `#${entityId}`]);
const targetRelations = relations.listableRelationsOf(entity);
//console.log("targetRelations", targetRelations, relations.relationsOf(entity));
// filter out polymorphic for now
//.filter((r) => r.type() !== "poly");
const local_relation_refs = relations
.sourceRelationsOf(entity)
?.map((r) => r.other(entity).reference);
const container = useEntity(entity.name, entityId, {
fetch: {
query: {
with: local_relation_refs
}
const $q = useEntityQuery(
entity.name,
entityId,
{
with: local_relation_refs
},
{
revalidateOnFocus: false
}
});
);
function goBack(state?: Record<string, any>) {
function goBack() {
window.history.go(-1);
}
@@ -52,43 +52,39 @@ export function DataEntityUpdate({ params }) {
return;
}
const res = await container.actions.update(changeSet);
console.log("update:res", res);
if (res.data?.error) {
setError(res.data.error);
} else {
error && setError(null);
try {
await $q.update(changeSet);
if (error) setError(null);
goBack();
} catch (e) {
setError(e instanceof Error ? e.message : "Failed to update");
}
}
async function handleDelete() {
if (confirm("Are you sure to delete?")) {
const res = await container.actions.remove();
if (res.error) {
setError(res.error);
} else {
error && setError(null);
try {
await $q._delete();
if (error) setError(null);
goBack();
} catch (e) {
setError(e instanceof Error ? e.message : "Failed to delete");
}
}
}
const data = $q.data;
const { Form, handleSubmit } = useEntityForm({
action: "update",
entity,
initialData: container.data,
initialData: $q.data?.toJSON(),
onSubmitted
});
//console.log("form.data", Form.state.values, container.data);
const makeKey = (key: string | number = "") =>
`${params.entity.name}_${entityId}_${String(key)}`;
const fieldsDisabled =
container.raw.fetch?.isLoading ||
container.status.fetch.isUpdating ||
Form.state.isSubmitting;
const fieldsDisabled = $q.isLoading || $q.isValidating || Form.state.isSubmitting;
return (
<Fragment key={makeKey()}>
@@ -103,7 +99,7 @@ export function DataEntityUpdate({ params }) {
onClick: () => {
bkndModals.open("debug", {
data: {
data: container.data as any,
data: data as any,
entity: entity.toJSON(),
schema: entity.toSchema(true),
form: Form.state.values,
@@ -165,7 +161,7 @@ export function DataEntityUpdate({ params }) {
entityId={entityId}
handleSubmit={handleSubmit}
fieldsDisabled={fieldsDisabled}
data={container.data ?? undefined}
data={data ?? undefined}
Form={Form}
action="update"
className="flex flex-grow flex-col gap-3 p-3"

View File

@@ -1,15 +1,16 @@
import { Type } from "core/utils";
import { useState } from "react";
import { useEntityMutate, useEntityQuery } from "ui/client";
import { useBknd } from "ui/client/BkndProvider";
import { Button } from "ui/components/buttons/Button";
import { type EntityData, useEntity } from "ui/container";
import { useBrowserTitle } from "ui/hooks/use-browser-title";
import { useSearch } from "ui/hooks/use-search";
import * as AppShell from "ui/layouts/AppShell/AppShell";
import { Breadcrumbs2 } from "ui/layouts/AppShell/Breadcrumbs2";
import { routes } from "ui/lib/routes";
import { EntityForm } from "ui/modules/data/components/EntityForm";
import { useEntityForm } from "ui/modules/data/hooks/useEntityForm";
import { useBknd } from "../../client/BkndProvider";
import { Button } from "../../components/buttons/Button";
import { type EntityData, useEntity } from "../../container";
import { useBrowserTitle } from "../../hooks/use-browser-title";
import { useSearch } from "../../hooks/use-search";
import * as AppShell from "../../layouts/AppShell/AppShell";
import { Breadcrumbs2 } from "../../layouts/AppShell/Breadcrumbs2";
import { routes } from "../../lib/routes";
export function DataEntityCreate({ params }) {
const { app } = useBknd();
@@ -17,40 +18,37 @@ export function DataEntityCreate({ params }) {
const [error, setError] = useState<string | null>(null);
useBrowserTitle(["Data", entity.label, "Create"]);
const container = useEntity(entity.name);
const $q = useEntityMutate(entity.name);
// @todo: use entity schema for prefilling
const search = useSearch(Type.Object({}), {});
console.log("search", search.value);
function goBack(state?: Record<string, any>) {
function goBack() {
window.history.go(-1);
}
async function onSubmitted(changeSet?: EntityData) {
console.log("create:changeSet", changeSet);
//return;
const res = await container.actions.create(changeSet);
console.log("create:res", res);
if (res.data?.error) {
setError(res.data.error);
} else {
error && setError(null);
if (!changeSet) return;
try {
await $q.create(changeSet);
if (error) setError(null);
// @todo: navigate to created?
goBack();
} catch (e) {
setError(e instanceof Error ? e.message : "Failed to create");
}
}
const { Form, handleSubmit, values } = useEntityForm({
const { Form, handleSubmit } = useEntityForm({
action: "create",
entity,
initialData: search.value,
onSubmitted
});
const fieldsDisabled =
container.raw.fetch?.isLoading ||
container.status.fetch.isUpdating ||
Form.state.isSubmitting;
const fieldsDisabled = $q.isLoading || $q.isValidating || Form.state.isSubmitting;
return (
<>

View File

@@ -1,12 +1,12 @@
import { Type } from "core/utils";
import { querySchema } from "data";
import { TbDots } from "react-icons/tb";
import { useApiQuery } from "ui/client";
import { useBkndData } from "ui/client/schema/data/use-bknd-data";
import { Button } from "ui/components/buttons/Button";
import { IconButton } from "ui/components/buttons/IconButton";
import { Message } from "ui/components/display/Message";
import { Dropdown } from "ui/components/overlay/Dropdown";
import { EntitiesContainer } from "ui/container";
import { useBrowserTitle } from "ui/hooks/use-browser-title";
import { useSearch } from "ui/hooks/use-search";
import * as AppShell from "ui/layouts/AppShell/AppShell";
@@ -25,19 +25,33 @@ const searchSchema = Type.Composite(
{ additionalProperties: false }
);
const PER_PAGE_OPTIONS = [5, 10, 25];
export function DataEntityList({ params }) {
const { $data, relations } = useBkndData();
const entity = $data.entity(params.entity as string);
const { $data } = useBkndData();
const entity = $data.entity(params.entity as string)!;
useBrowserTitle(["Data", entity?.label ?? params.entity]);
const [navigate] = useNavigate();
const search = useSearch(searchSchema, {
select: entity?.getSelect(undefined, "table") ?? [],
sort: entity?.getDefaultSort()
});
console.log("search", search.value);
useBrowserTitle(["Data", entity?.label ?? params.entity]);
const PER_PAGE_OPTIONS = [5, 10, 25];
//console.log("search", search.value);
const $q = useApiQuery(
(api) =>
api.data.readMany(entity.name, {
select: search.value.select,
limit: search.value.perPage,
offset: (search.value.page - 1) * search.value.perPage,
sort: search.value.sort
}),
{
revalidateOnFocus: true,
keepPreviousData: true
}
);
const data = $q.data?.data;
const meta = $q.data?.body.meta;
function handleClickRow(row: Record<string, any>) {
if (entity) navigate(routes.data.entity.edit(entity.name, row.id));
@@ -65,6 +79,8 @@ export function DataEntityList({ params }) {
return <Message.NotFound description={`Entity "${params.entity}" doesn't exist.`} />;
}
const isUpdating = $q.isLoading && $q.isValidating;
return (
<>
<AppShell.SectionHeader
@@ -103,45 +119,25 @@ export function DataEntityList({ params }) {
<SearchInput placeholder={`Filter ${entity.label}`} />
</div>*/}
<EntitiesContainer
entity={entity.name}
query={{
select: search.value.select,
limit: search.value.perPage,
offset: (search.value.page - 1) * search.value.perPage,
sort: search.value.sort
}}
<div
data-updating={isUpdating ? 1 : undefined}
className="data-[updating]:opacity-50 transition-opacity pb-10"
>
{(params) => {
if (params.status.fetch.isLoading) {
return null;
}
const isUpdating = params.status.fetch.isUpdating;
return (
<div
data-updating={isUpdating ? 1 : undefined}
className="data-[updating]:opacity-50 transition-opacity pb-10"
>
<EntityTable2
data={params.data ?? []}
entity={entity}
select={search.value.select}
onClickRow={handleClickRow}
page={search.value.page}
sort={search.value.sort}
onClickSort={handleSortClick}
perPage={search.value.perPage}
perPageOptions={PER_PAGE_OPTIONS}
total={params.meta?.count}
onClickPage={handleClickPage}
onClickPerPage={handleClickPerPage}
/>
</div>
);
}}
</EntitiesContainer>
<EntityTable2
data={data ?? null}
entity={entity}
/*select={search.value.select}*/
onClickRow={handleClickRow}
page={search.value.page}
sort={search.value.sort}
onClickSort={handleSortClick}
perPage={search.value.perPage}
perPageOptions={PER_PAGE_OPTIONS}
total={meta?.count}
onClickPage={handleClickPage}
onClickPerPage={handleClickPerPage}
/>
</div>
</div>
</AppShell.Scrollable>
</>