Skip to content

Commit

Permalink
Merge pull request #167 from mblajek/FZ-84-menu
Browse files Browse the repository at this point in the history
FZ-84 Created a more solid logic of switching facilities.
  • Loading branch information
TPReal authored Nov 13, 2023
2 parents 0232555 + 8b73d14 commit 7abdb7f
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 64 deletions.
31 changes: 31 additions & 0 deletions resources/js/components/utils/one_time_effect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {createEffect} from "solid-js";
import {Accessor, createSignal} from "solid-js";

/**
* Creates an effect that runs once, when the input returns a non-undefined value for the first time
* (which may be immediately).
*
* Returns a function that can be called to reënable the effect for another single call of
* the effect function.
*/
export function createOneTimeEffect<T>({
input,
effect,
initialActive = true,
}: {
input: Accessor<T | undefined>;
effect: (value: T) => void;
initialActive?: boolean;
}) {
const [active, setActive] = createSignal(initialActive);
createEffect(() => {
if (active()) {
const inputVal = input();
if (inputVal !== undefined) {
effect(inputVal);
setActive(false);
}
}
});
return () => setActive(true);
}
9 changes: 6 additions & 3 deletions resources/js/data-access/memo-api/groups/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {parseGetResponse} from "../utils";
* @see {@link http://localhost:9081/api/documentation#/User local docs}
*/
export namespace User {
const getStatus = (facilityId?: string, config?: Api.Config) =>
const getStatus = (facilityId?: Api.Id, config?: Api.Config) =>
V1.get<Api.Response.Get<GetStatusData>>(facilityId ? `/user/status/${facilityId}` : "/user/status", config).then(
parseGetResponse,
);
Expand All @@ -23,6 +23,9 @@ export namespace User {
export const changePassword = (data: ChangePasswordRequest, config?: Api.Config) =>
V1.post<Api.Response.Post>("/user/password", data, config);

export const setLastLoginFacilityId = (lastLoginFacilityId: Api.Id, config?: Api.Config) =>
V1.patch("/user", {lastLoginFacilityId}, config);

export type GetStatusData = {
user: UserResource;
permissions: PermissionsResource;
Expand All @@ -43,7 +46,7 @@ export namespace User {
export const keys = {
all: () => ["user"] as const,
statusAll: () => [...keys.all(), "status"] as const,
status: (facilityId?: string) => [...keys.statusAll(), facilityId] as const,
status: (facilityId?: Api.Id) => [...keys.statusAll(), facilityId] as const,
};

type PermissionsFacilityKeys = "facilityId" | "facilityMember" | "facilityClient" | "facilityStaff" | "facilityAdmin";
Expand Down Expand Up @@ -77,7 +80,7 @@ export namespace User {
}) satisfies SolidQueryOptions<GetStatusWithoutFacilityData>;

/** Query options for user status with facility permissions. */
export const statusWithFacilityPermissionsQueryOptions = (facilityId: string) =>
export const statusWithFacilityPermissionsQueryOptions = (facilityId: Api.Id) =>
({
queryFn: ({signal}) => getStatus(facilityId, {signal}),
queryKey: keys.status(facilityId),
Expand Down
17 changes: 2 additions & 15 deletions resources/js/features/authentication/pages/Login.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,8 @@ import {LoginForm} from "../forms/login/Login.form";
*/
const LoginPage: VoidComponent = () => {
const statusQuery = createQuery(User.statusQueryOptions);

createEffect(() => {
if (statusQuery.isSuccess) {
if (statusQuery.data.user.lastLoginFacilityId) setActiveFacilityId(statusQuery.data.user.lastLoginFacilityId);
}
});

createEffect(() => {
LoginForm.showModal(statusQuery.isError);
});

onMount(() => {
setActiveFacilityId(undefined);
});

onMount(() => setActiveFacilityId(undefined));
createEffect(() => LoginForm.showModal(statusQuery.isError));
return (
<Page title="Logowanie">
<LoginForm.LoginModal />
Expand Down
81 changes: 41 additions & 40 deletions resources/js/features/root/components/header/FacilityControl.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,62 @@
import {useNavigate, useParams} from "@solidjs/router";
import {createQuery} from "@tanstack/solid-query";
import {Select} from "components/ui/form/Select";
import {createOneTimeEffect} from "components/utils/one_time_effect";
import {System, User} from "data-access/memo-api/groups";
import {For, Match, Show, Switch, VoidComponent, createEffect, createMemo} from "solid-js";
import {Match, Show, Switch, VoidComponent, createMemo} from "solid-js";
import {activeFacilityId, setActiveFacilityId} from "state/activeFacilityId.state";

export const FacilityControl: VoidComponent = () => {
const navigate = useNavigate();
const params = useParams();

const facilitiesQuery = createQuery(System.facilitiesQueryOptions);
const statusQuery = createQuery(User.statusQueryOptions);

const facilities = createMemo(
const invalidate = User.useInvalidator();
const userFacilities = createMemo(
() =>
facilitiesQuery.data?.filter(
(facility) => statusQuery.data?.members.find((member) => member.facilityId === facility.id),
),
facilitiesQuery.data
?.filter((facility) => statusQuery.data?.members.find((member) => member.facilityId === facility.id))
.sort((a, b) => a.name.localeCompare(b.name)),
);

// TODO: it just works, may be wrong. Maybe there is a better way of handling 'global mutable state' :D
createEffect(() => {
if (params.facilityUrl) {
const facility = facilities()?.find((facility) => facility.url === params.facilityUrl);

if (!facility) return;

if (statusQuery.data?.members.find((member) => member.facilityId === facility.id))
setActiveFacilityId(facility.id);
}
createOneTimeEffect({
input: () => {
const facilities = userFacilities();
if (!facilities || !statusQuery.isSuccess) {
return undefined;
}
// Use the facility from the URL, if not present (e.g. not on a facility-specific page) use the
// last login facility, and finally use any (the first) facility, so that some facility is always
// selected.
return (
facilities.find(({url}) => url === params.facilityUrl) ||
facilities.find(({id}) => id === statusQuery.data!.user.lastLoginFacilityId) ||
facilities[0]
)?.id;
},
effect: setActiveFacilityId,
});

// TODO: it just works, may be wrong. Maybe there is a better way of handling 'global mutable state' :D
createEffect(() => {
const facilitiesSub = facilities();
if (facilitiesSub?.length === 1) setActiveFacilityId(facilitiesSub[0]?.id);
});

return (
<Show when={facilities()}>
{(facilities) => (
<Show when={userFacilities()}>
{(userFacilities) => (
<Switch>
<Match when={facilities().length === 1}>
<p>{facilities().at(0)?.name}</p>
<Match when={userFacilities().length === 1}>
<p>{userFacilities()[0]!.name}</p>
</Match>
<Match when={facilities().length > 1}>
<select
class="border border-gray-200 rounded"
<Match when={userFacilities().length > 1}>
<Select
name="activeFacilityId"
items={userFacilities().map(({id, name}) => ({value: id, label: () => name}))}
nullable={false}
value={activeFacilityId()}
onChange={(e) => {
const url = facilities().find((facility) => facility.id === e.target.value)?.url;

setActiveFacilityId(e.target.value);
if (url) navigate(`/${url}`);
onValueChange={(facilityId) => {
if (facilityId !== activeFacilityId()) {
setActiveFacilityId(facilityId);
const url = userFacilities().find((facility) => facility.id === facilityId)?.url;
if (url) navigate(`/${url}`);
}
User.setLastLoginFacilityId(facilityId!).then(() => invalidate.statusAndFacilityPermissions());
}}
>
<For each={facilities()}>{(facility) => <option value={facility.id}>{facility.name}</option>}</For>
</select>
/>
</Match>
</Switch>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {A} from "@solidjs/router";
import {Button} from "components/ui/Button";
import {AUTO_SIZE_COLUMN_DEFS, createTableTranslations} from "components/ui/Table";
import {TQueryTable} from "components/ui/Table/TQueryTable";
Expand Down Expand Up @@ -30,11 +29,7 @@ export default (() => {
},
url: {
columnDef: {
cell: (c) => {
const href = () => `/${c.getValue()}`;
// TODO: The link can be inaccessible for the current user, handle this better.
return <A href={href()}>{href()}</A>;
},
cell: (c) => `/${c.getValue()}`,
},
},
createdAt: {
Expand Down

0 comments on commit 7abdb7f

Please sign in to comment.