add Winder tab

- use generic icons in bars
- add useMachines hooks
This commit is contained in:
Icedsoda committed 2025-03-12 23:18:04 +01:00
1 parent aa845c5629
commit 46cdcbaf5c
13 files changed
+139 -64

No files matched your search

+3 -2
View File
@@ -1,8 +1,9 @@
import React from "react";
type Props = {
children: React.ReactNode;
title?: string;
};
export function Page({ children }: Props) {
return <div className="flex flex-col p-6 gap-2 ">{children}</div>;
return <div className="flex flex-col gap-2 p-6">{children}</div>;
}
+16 -13
View File
@@ -1,14 +1,15 @@
import { OutsideCorner } from "@/components/OutsideCorner";
import { useMachines } from "@/hooks/useMachines";
import { useOnSubpath } from "@/hooks/useOnSubpath";
import { Link, Outlet } from "@tanstack/react-router";
import { Wrench } from "lucide-react";
import { Fragment } from "react";
import React from "react";
import { Icon, IconName } from "./Icon";
type SidebarItemContent = {
link: string;
activeLink: string;
icon?: React.ReactNode;
icon?: IconName;
title: string;
};
@@ -31,10 +32,10 @@ export function SidebarItem({
>
<div
className={`text-md relative z-10 flex h-full w-full items-center justify-center gap-2 ${
isActive ? "rounded-l-lg bg-white" : "rounded-lg bg-neutral-100"
isActive ? "rounded-l-lg bg-white pr-2" : "rounded-lg bg-neutral-100"
}`}
>
{icon}
{icon && <Icon name={icon} />}
{title}
</div>
{isActive && <OutsideCorner rightTop={!isFirst} rightBottom={true} />}
@@ -43,26 +44,28 @@ export function SidebarItem({
}
export function SidebarLayout() {
const machines = useMachines();
const items: SidebarItemContent[] = [
{
link: "/_sidebar/machines/123",
activeLink: "/_sidebar/machines/123",
title: "Winder 123",
},
...machines.map((machine) => ({
link: `/_sidebar/machines/${machine.slug}/${machine.machine_identification.serial}/control`,
activeLink: `/_sidebar/machines/${machine.slug}/${machine.machine_identification.serial}`,
title: machine.name,
icon: machine.icon,
})),
{
link: "/_sidebar/setup/ethercat",
activeLink: "/_sidebar/setup",
title: "Setup",
icon: <Wrench size={20} />,
icon: "lu:Settings2",
},
];
return (
<div className="flex h-full flex-row">
<div className="w-40" />
<div className="fixed flex h-full w-40 flex-col bg-neutral-200">
<div className="w-48 min-w-48" />
<div className="fixed flex h-full w-48 flex-col bg-neutral-200">
<div className="flex h-20 flex-col items-center justify-center gap-0 pt-2">
<div className="font-qitech line-clamp-none text-3xl">QiTech</div>
<div className="font-qitech line-clamp-none text-3xl">QITECH</div>
</div>
<div className="flex flex-col gap-2">
{items.map((item, index) => (
+3 -13
View File
@@ -4,21 +4,11 @@ import { OutsideCorner } from "./OutsideCorner";
import { BackButton } from "./BackButton";
import { Link, Outlet } from "@tanstack/react-router";
import { useOnSubpath } from "@/hooks/useOnSubpath";
// type TopbarItemProps = {
// icon?: React.ReactNode;
// title: string;
// } & SidebarButtonBuilderProps;
// export type SidebarButtonBuilderProps = {
// isActive: boolean;
// isFirst: boolean;
// onClick: () => void;
// };
import { Icon, IconName } from "./Icon";
type TopbarItemContent = {
link: string;
icon?: React.ReactNode;
icon?: IconName;
title: string;
};
@@ -32,7 +22,7 @@ export function TopbarItem({ icon, title, link }: TopbarItemProps) {
isActive ? "rounded-t-lg bg-white pb-2" : "rounded-lg bg-neutral-100"
}`}
>
{icon}
{icon && <Icon name={icon} />}
{title}
</div>
<OutsideCorner bottomLeft={isActive} bottomRight={isActive} />
+44
View File
@@ -0,0 +1,44 @@
import {
getVendorPreset,
getMachinePreset,
MachinePreset,
VendorPreset,
} from "@/machines/types";
import {
EthercatSetupEventMachineInfo,
useSocketioEthercatSetupEvent,
} from "./useSocketio";
type UseMachine = {
machine_identification: EthercatSetupEventMachineInfo["machine_identification"];
name: MachinePreset["name"];
version: MachinePreset["version"];
slug: MachinePreset["slug"];
vendor: VendorPreset["name"];
icon: MachinePreset["icon"];
};
// returns only valid mahcines
export function useMachines(): UseMachine[] {
const event = useSocketioEthercatSetupEvent();
return (
event.data?.machine_infos
.filter((machine) => machine.error === null)
.map((machine) => {
const machinePreset = getMachinePreset(machine.machine_identification);
const vendorPreset = getVendorPreset(machinePreset!.vendor_id);
if (!machinePreset || !vendorPreset) {
return undefined;
}
return {
machine_identification: machine.machine_identification,
name: machinePreset.name,
version: machinePreset.version,
slug: machinePreset.slug,
vendor: vendorPreset.name,
icon: machinePreset.icon,
};
})
.filter((machine) => machine !== undefined) || []
);
}
+26 -5
View File
@@ -1,9 +1,18 @@
// every device has multiple roles to indentify the function of a subdevice
// each role can only be given once
import { IconName } from "@/components/Icon";
// EK1100 should have role 0
export type DeviceRole = {
role: number;
// UI purpose
role_label: string;
// what kind of subdevices can be assigned to this role
allowed_devices: EthercatDevice[];
};
// data to identify subdevices
type EthercatDevice = {
product_id: number;
revision: number;
@@ -16,33 +25,45 @@ export type MachineIdentification = {
};
export type MachinePreset = {
// displayable name
name: string;
// displayable version
version: string;
// path for IO routes
slug: string;
icon: IconName;
// needs to be same as in the backend
vendor_id: 0x0001;
// needs to be same as in the backend
machine_id: number;
// roles and thair allowed devices
device_roles: DeviceRole[];
};
export const VENDOR_QITECH = 0x0001;
export type Vendor = {
export type VendorPreset = {
id: number;
name: string;
};
export const vendors: Vendor[] = [
export const vendorPresets: VendorPreset[] = [
{
id: VENDOR_QITECH,
name: "QiTech Industries GmbH",
},
];
export function get_vendor_name(vendor_id: number): Vendor | undefined {
return vendors.find((v) => v.id === vendor_id);
export function getVendorPreset(vendor_id: number): VendorPreset | undefined {
return vendorPresets.find((v) => v.id === vendor_id);
}
export const machinePresets: MachinePreset[] = [
{
name: "Winder V1",
name: "Winder",
version: "V1",
slug: "winder1",
icon: "lu:Shell",
vendor_id: VENDOR_QITECH,
machine_id: 0x0001,
device_roles: [
@@ -0,0 +1,8 @@
import { Page } from "@/components/Page";
import { winder1SerialRoute } from "@/routes/routes";
import React from "react";
export function Winder1ControlPage() {
const { serial } = winder1SerialRoute.useParams();
return <Page>Winder 1 Control</Page>;
}
+12 -5
View File
@@ -1,11 +1,18 @@
import { Outlet } from "@tanstack/react-router";
import { Topbar } from "@/components/Topbar";
import { winder1SerialRoute } from "@/routes/routes";
import React from "react";
export function Winder1Page() {
const { serial } = winder1SerialRoute.useParams();
return (
<>
<h1>Winder1Page</h1>
<Outlet />
</>
<Topbar
pathname={`/_sidebar/machines/winder1/${serial}`}
items={[
{
link: "control",
title: "Control",
},
]}
/>
);
}
+1 -1
View File
@@ -8,6 +8,6 @@ declare module "@tanstack/react-router" {
}
const history = createMemoryHistory({
initialEntries: ["/_sidebar/machines/123/winder1/general"],
initialEntries: ["/_sidebar/setup/ethercat"],
});
export const router = createRouter({ routeTree: rootTree, history: history });
+15 -15
View File
@@ -2,16 +2,14 @@ import { createRoute } from "@tanstack/react-router";
import { RootRoute } from "./__root";
import React from "react";
import { SidebarLayout } from "@/components/SidebarLayout";
import { Winder1Page } from "@/machines/winder1/Winder1Page";
import { SetupPage } from "@/setup/SetupPage";
import { EthercatPage } from "@/setup/EthercatPage";
import { MachinesPage } from "@/setup/MachinesPage";
import { Winder1Page } from "@/machines/winder1/Winder1Page";
import { Winder1ControlPage } from "@/machines/winder1/Winder1ControlPage";
// make a route tree like this
// _mainNavigation/machines/$machineSerial/winder1/general
// _mainNavigation/machines/$machineSerial/winder1/handbook
// _mainNavigation/machines/$machineSerial/winder2/general
// _mainNavigation/machines/$machineSerial/winder2/handbook
// _mainNavigation/machines/winder1/$serial/control
// _mainNavigation/configuration/a
// _mainNavigation/configuration/b
// the mainNavigation has a custom layout
@@ -24,21 +22,21 @@ export const sidebarRoute = createRoute({
component: () => <SidebarLayout />,
});
export const machineRoute = createRoute({
export const machinesRoute = createRoute({
getParentRoute: () => sidebarRoute,
path: "machines/$machineSerial",
path: "machines",
});
export const winder1Route = createRoute({
export const winder1SerialRoute = createRoute({
getParentRoute: () => machinesRoute,
path: "winder1",
path: "winder1/$serial",
component: () => <Winder1Page />,
});
export const winder1GeneralRoute = createRoute({
getParentRoute: () => winder1Route,
path: "general",
component: () => <div>winder1 general</div>,
getParentRoute: () => winder1SerialRoute,
path: "control",
component: () => <Winder1ControlPage />,
});
export const setupRoute = createRoute({
@@ -53,7 +51,7 @@ export const ethercatRoute = createRoute({
component: () => <EthercatPage />,
});
export const machinesRoute = createRoute({
export const setupMachinesRoute = createRoute({
getParentRoute: () => setupRoute,
path: "machines",
component: () => <MachinesPage />,
@@ -61,7 +59,9 @@ export const machinesRoute = createRoute({
export const rootTree = RootRoute.addChildren([
sidebarRoute.addChildren([
machineRoute.addChildren([winder1Route.addChildren([winder1GeneralRoute])]),
setupRoute.addChildren([ethercatRoute, machinesRoute]),
setupRoute.addChildren([ethercatRoute, setupMachinesRoute]),
machinesRoute.addChildren([
winder1SerialRoute.addChildren([winder1GeneralRoute]),
]),
]),
]);
+1 -1
View File
@@ -190,7 +190,7 @@ export function DeviceEeepromDialogContent({ device, setOpen }: ContentProps) {
key={machine.machine_id}
value={machine.machine_id.toString()}
>
{machine.name}
{machine.name} {machine.version}
</SelectItem>
))}
</SelectContent>
+3 -2
View File
@@ -52,7 +52,8 @@ export const columns: ColumnDef<EthercatSetupEvent["devices"][0]>[] = [
if (!machine_identification) {
return "—";
}
return getMachinePreset(machine_identification)?.name ?? "UNKNOWN";
const machinePreset = getMachinePreset(machine_identification);
return machinePreset?.name + " " + machinePreset?.version;
},
},
{
@@ -114,7 +115,7 @@ export function EthercatPage() {
});
return (
<Page title="EtherCAT">
<Page>
<p>
Maschine, Maschinen Seriennummer, Rolle sind QiTech spezifische Werte
die in den EEPROM geschrieben werden um Maschinen als Einheit zu
+5 -4
View File
@@ -13,7 +13,7 @@ import {
useReactTable,
} from "@tanstack/react-table";
import React, { useMemo } from "react";
import { get_vendor_name, getMachinePreset } from "@/machines/types";
import { getVendorPreset, getMachinePreset } from "@/machines/types";
import { IconText } from "@/components/IconText";
export const columns: ColumnDef<EthercatSetupEventMachineInfo>[] = [
@@ -25,7 +25,8 @@ export const columns: ColumnDef<EthercatSetupEventMachineInfo>[] = [
if (!machine_identification) {
return "—";
}
return getMachinePreset(machine_identification)?.name ?? "UNKNOWN";
const machinePreset = getMachinePreset(machine_identification);
return machinePreset?.name + " " + machinePreset?.version;
},
},
{
@@ -36,7 +37,7 @@ export const columns: ColumnDef<EthercatSetupEventMachineInfo>[] = [
if (!machine_identification) {
return "—";
}
return get_vendor_name(machine_identification.vendor)?.name ?? "UNKNOWN";
return getVendorPreset(machine_identification.vendor)?.name ?? "UNKNOWN";
},
},
{
@@ -81,7 +82,7 @@ export function MachinesPage() {
});
return (
<Page title="EtherCAT">
<Page>
<SectionTitle title="Maschinen">
<RefreshIndicator messageResponse={deviceMessage} />
</SectionTitle>
+2 -3
View File
@@ -1,5 +1,4 @@
import { Topbar } from "@/components/Topbar";
import { EthernetPort, Factory } from "lucide-react";
import React from "react";
export function SetupPage() {
@@ -10,12 +9,12 @@ export function SetupPage() {
{
link: "ethercat",
title: "Ethercat",
icon: <EthernetPort size={20} />,
icon: "lu:EthernetPort",
},
{
link: "machines",
title: "Maschinen",
icon: <Factory size={20} />,
icon: "lu:Factory",
},
]}
/>