add Winder tab
- use generic icons in bars - add useMachines hooks
This commit is contained in:
1 parent
aa845c5629
commit
46cdcbaf5c
13 files changed
+139
-64
No files matched your search
@@ -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>;
|
||||
}
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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) || []
|
||||
);
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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 });
|
||||
@@ -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]),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user