Skip to content

Commit

Permalink
Improve SSR, refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
BlueManCZ committed Jan 8, 2025
1 parent ac8ca0a commit 28bce24
Show file tree
Hide file tree
Showing 37 changed files with 445 additions and 382 deletions.
1 change: 1 addition & 0 deletions src/client/apps/contwatch-client/app/[lang]/globals.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@use "../../../../packages/ui/src/components/common";
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const PageRenderer: FC<PageRendererProps> = ({ handlerId }) => {
<Text size={"medium"} weight={"bold"}>
<Link href={"/handlers"}>{t("Handlers")}</Link> · {handler?.name}
</Text>
<HandlersWrapper>{handler && <HandlerWidget handler={handler} editMode />}</HandlersWrapper>
<HandlersWrapper>{handler && <HandlerWidget handlerId={handler.id} editMode />}</HandlersWrapper>
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PageRenderer } from "./components/PageRenderer/PageRenderer";

export default async function Handlers({ params }: { params: Promise<{ id: number }> }) {
return <PageRenderer handlerId={(await params).id} />;
export default async function Handlers({ params }: { params: Promise<{ handlerId: number }> }) {
return <PageRenderer handlerId={(await params).handlerId} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import {
closestCenter,
DndContext,
type DragEndEvent,
KeyboardSensor,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from "@dnd-kit/core";
import { restrictToParentElement, restrictToVerticalAxis } from "@dnd-kit/modifiers";
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { Flex } from "@repo/ui/Flex";
import { Column } from "@repo/ui/FlexPartials";
import { Icon } from "@repo/ui/Icon";
import { Text } from "@repo/ui/Text";
import { bemClassNames } from "@repo/utils/bemClassNames";
import { executeRequest } from "@repo/utils/communication";
import { Endpoint } from "@repo/utils/endpoints";
import { getApiEndpoint } from "@repo/utils/getApiEndpoint";
import { useTranslation } from "@repo/utils/useTranslation";
import { DateTime } from "luxon";
import Link from "next/link";
import { type FC, useEffect, useState } from "react";
import { useSWRConfig } from "swr";

import { useHandler, useHandlerAttributes } from "../../../swrEndpoints";
import { AttributeWidget } from "../AttributeWidget/AttributeWidget";
import styles from "./HandlerWidget.module.scss";

type HandlerWidgetProps = {
handlerId: number;
editMode?: boolean;
};

const bem = bemClassNames(styles);

export const HandlerWidget: FC<HandlerWidgetProps> = ({ handlerId, editMode }) => {
const { t } = useTranslation();
const { mutate } = useSWRConfig();

const { data: handler } = useHandler(handlerId);
const { data: attributes } = useHandlerAttributes(handler?.id ?? 0);

const [attributeIds, setAttributeIds] = useState<number[]>([]);

useEffect(() => {
if (handler?.attributes) {
setAttributeIds(handler.attributes);
}
}, [handler?.attributes]);

const sensors = useSensors(
useSensor(MouseSensor, {
activationConstraint: {
distance: 8,
},
}),
useSensor(TouchSensor, {
activationConstraint: {
delay: 300,
tolerance: 8,
},
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
);

const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;

if (active.id !== over?.id) {
setAttributeIds((items) => {
const oldIndex = items.indexOf(active.id as number);
const newIndex = items.indexOf(over?.id as number);

const newItems = arrayMove(items, oldIndex, newIndex);

executeRequest(getApiEndpoint(Endpoint.attributesSetOrder), "POST", newItems);

return newItems;
});
}
};

return (
handler && (
<Column className={bem()}>
<Link href={`/handlers/${handler.id}`} className={bem("header", { color: handler.status })}>
<Icon icon={handler.icon} invert />
<Column grow width={0}>
<Text weight={"bold"} nowrap ellipsis>
{handler.name}
</Text>
<Text size={"tiny"} nowrap ellipsis>
{handler.description}
</Text>
</Column>
<Column>
<Text size={"tiny"} align={"right"} nowrap>
{t("Data received")}:{" "}
<b>
{handler.last_message !== null
? (handler.last_message ?? 0) < 10
? t("Now")
: DateTime.local()
.minus({ seconds: handler.last_message })
.toRelative()
: t("Never")}
</b>
</Text>
<Text size={"tiny"} align={"right"} nowrap>
{t("Handler ID")}: <b>{handler.id}</b>
</Text>
</Column>
</Link>
{(attributeIds.length > 0 ||
(editMode &&
handler.availableAttributes.filter((attribute) =>
attributes?.every((a) => a.name !== attribute.name),
).length > 0)) && (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
modifiers={[restrictToVerticalAxis, restrictToParentElement]}
onDragStart={() => {
navigator.vibrate?.(40);
}}
>
<SortableContext items={attributeIds} strategy={verticalListSortingStrategy}>
<div className={bem("body")}>
{editMode && attributeIds.length > 0 && (
<Flex padding={"half-rem"} margin={"horizontal-half-rem"}>
<Text size={"tiny"} weight={"medium"} color={"silver"} uppercase>
{t("Stored attributes")}
</Text>
</Flex>
)}
{attributeIds?.map((attributeId) => {
return (
<AttributeWidget
draggable
key={attributeId}
handlerId={handler.id}
{...{ attributeId, editMode }}
/>
);
})}
{editMode &&
handler.availableAttributes.filter((attribute) =>
attributes?.every((a) => a.name !== attribute.name),
).length > 0 && (
<Flex padding={"half-rem"} margin={"horizontal-half-rem"}>
<Text size={"tiny"} weight={"medium"} color={"silver"} uppercase>
{t("Available attributes")}
</Text>
</Flex>
)}
<Column gap={".1rem"}>
{editMode &&
handler.availableAttributes
.filter((attribute) =>
attributes?.every((a) => a.name !== attribute.name),
)
?.map((attribute) => {
return (
<Flex
key={attribute.name}
padding={"half-rem"}
margin={"horizontal-half-rem"}
alignItems={"center"}
gap={".5rem"}
>
<Icon icon={"circle"} variant={"circle"} />
<Column grow>
<Text size={"small"} weight={"bold"} nowrap>
{attribute.name}
</Text>
<Text size={"tiny"} nowrap>
{t("Latest value")}: <b>{attribute.value}</b>
</Text>
</Column>
<Icon
icon={"plus"}
variant={"circle"}
onClick={() => {
executeRequest(
getApiEndpoint(
Endpoint.addHandlerAttribute,
),
"POST",
{
handler_id: handler.id,
attribute: attribute.name,
},
async () => {
await mutate(
`${getApiEndpoint(Endpoint.handlers)}/${handler.id}`,
);
await mutate(
getApiEndpoint(
Endpoint.attributes,
`?handler=${handler.id}`,
),
);
},
);
}}
/>
</Flex>
);
})}
</Column>
</div>
</SortableContext>
</DndContext>
)}
</Column>
)
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@
import { Text } from "@repo/ui/Text";
import { useTranslation } from "@repo/utils/useTranslation";

import { useHandlers } from "../swrEndpoints";
import { useHandlerIds } from "../swrEndpoints";
import { HandlersWrapper } from "./components/HandlersWrapper/HandlersWrapper";
import { HandlerWidget } from "./components/HandlerWidget/HandlerWidget";

export default function Handlers() {
const { t } = useTranslation();
const { data: handlers } = useHandlers();
const { data: handlerIds } = useHandlerIds(); // TODO: Remove and make this SSR

return (
<>
<Text size={"medium"} weight={"bold"}>
{t("Handlers")}
</Text>
<HandlersWrapper>
{handlers?.map((handler) => (
<HandlerWidget key={handler.id} {...{ handler }} />
{handlerIds?.map((handlerId) => (
<HandlerWidget key={handlerId} {...{ handlerId }} />
))}
</HandlersWrapper>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import "./globals.scss";

import type { PageProps } from "@repo/types/PageProps";
import { openSans } from "@repo/ui/fonts";
import { NavbarLayout } from "@repo/ui/NavbarLayout";
import type { Metadata } from "next";
Expand All @@ -12,11 +13,16 @@ export const metadata: Metadata = {
description: "Scalable system for IoT automation.",
};

export default function RootLayout({ children }: PropsWithChildren) {
export async function generateStaticParams() {
return [{ lang: "cs" }, { lang: "en" }];
}

export default async function RootLayout({ children, params }: PropsWithChildren<PageProps>) {
const lang = (await params).lang;
return (
<html lang="en">
<html lang={lang}>
<body className={openSans.className}>
<Providers>
<Providers lang={lang}>
<NavbarLayout>
<Suspense>{children}</Suspense>
</NavbarLayout>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
"use client";

import type { PageProps } from "@repo/types/PageProps";
import { Button } from "@repo/ui/Button";
import { Flex } from "@repo/ui/Flex";
import { Text } from "@repo/ui/Text";
import { useTranslation } from "@repo/utils/useTranslation";
import { ssrTranslation } from "@repo/utils/ssrTranslation";

export default function Overview() {
// TODO: Use SSR translation and remove "use client"
const { t } = useTranslation();
export default async function Overview({ params }: PageProps) {
const lang = (await params).lang;
const { t } = await ssrTranslation(lang);

return (
<>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
"use client";

import { StoreProvider } from "@repo/store/StoreProvider";
// import { ThemeProvider } from "next-themes";
import type { PropsWithChildren } from "react";

export function Providers({ children }: PropsWithChildren) {
export function Providers({ children, lang }: PropsWithChildren<{ lang: string }>) {
return (
// <ThemeProvider
// defaultTheme="system"
Expand All @@ -14,7 +12,7 @@ export function Providers({ children }: PropsWithChildren) {
// }}
// enableSystem
// >
<StoreProvider>{children}</StoreProvider>
<StoreProvider lang={lang}>{children}</StoreProvider>
// </ThemeProvider>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { Endpoint } from "@repo/utils/endpoints";
import { getApiEndpoint } from "@repo/utils/getApiEndpoint";
import useSWR from "swr";

export const useHandlers = () => {
return useSWR<HandlerModel[]>(getApiEndpoint(Endpoint.handlers), getJson);
export const useHandlerIds = () => {
return useSWR<number[]>(getApiEndpoint(Endpoint.handlers), getJson);
};

export const useHandler = (id: number) => {
Expand Down
1 change: 0 additions & 1 deletion src/client/apps/contwatch-client/app/globals.scss

This file was deleted.

Loading

0 comments on commit 28bce24

Please sign in to comment.