Skip to content

Commit

Permalink
Merge pull request #617 from sinamics/routes
Browse files Browse the repository at this point in the history
Added new table layout and notes for Managed Routes.
  • Loading branch information
sinamics authored Dec 28, 2024
2 parents 40be454 + 3c316a7 commit f4c1eb3
Show file tree
Hide file tree
Showing 27 changed files with 1,078 additions and 315 deletions.
22 changes: 22 additions & 0 deletions prisma/migrations/20241227084426_routes_table/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
Warnings:
- You are about to drop the column `routes` on the `network` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "network" DROP COLUMN "routes";

-- CreateTable
CREATE TABLE "Routes" (
"id" TEXT NOT NULL,
"target" TEXT NOT NULL,
"via" TEXT,
"networkId" TEXT NOT NULL,
"notes" TEXT,

CONSTRAINT "Routes_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "Routes" ADD CONSTRAINT "Routes_networkId_fkey" FOREIGN KEY ("networkId") REFERENCES "network"("nwid") ON DELETE CASCADE ON UPDATE CASCADE;
11 changes: 10 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,16 @@ model network {
organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
networkMembers network_members[]
notations Notation[]
routes Json?
routes Routes[]
}

model Routes {
id String @id @default(cuid())
target String
via String?
networkId String
notes String?
network network @relation(fields: [networkId], references: [nwid], onDelete: Cascade)
}

model Notation {
Expand Down
10 changes: 7 additions & 3 deletions src/__tests__/pages/network/[id].test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,10 @@ describe("NetworkById component", () => {
</NextIntlClientProvider>
</QueryClientProvider>,
);
// await waitForElementToBeRemoved(() => screen.queryByText(/loading/i));
// Ensure table is present
screen.getByRole("table"); // This will throw an error if the table is not present

// Ensure tables is present
screen.getByRole("membersTable");
screen.getByRole("routesTable");

await waitFor(
() => {
Expand All @@ -290,6 +291,7 @@ describe("NetworkById component", () => {
{
id: "member_id",
creationTime: 1691603143446,
ipAssignments: ["10.121.15.31"],
lastSeen: new Date(Date.now() - 4 * 60 * 1000).toISOString(),
conStatus: ConnectionStatus.DirectWAN,
},
Expand Down Expand Up @@ -330,6 +332,7 @@ describe("NetworkById component", () => {
{
id: "member_id",
creationTime: 1691603143446,
ipAssignments: ["10.121.15.31"],
lastSeen: new Date(Date.now() - 4 * 60 * 1000).toISOString(),
conStatus: ConnectionStatus.Relayed,
},
Expand Down Expand Up @@ -370,6 +373,7 @@ describe("NetworkById component", () => {
{
id: "member_id",
creationTime: 1691603143446,
ipAssignments: ["10.121.15.31"],
lastSeen: new Date(Date.now() - 4 * 60 * 1000).toISOString(),
conStatus: ConnectionStatus.Offline,
},
Expand Down
18 changes: 3 additions & 15 deletions src/components/elements/input.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
import { forwardRef, useEffect, useState } from "react";
import { ComponentPropsWithRef, forwardRef, useEffect, useState } from "react";
import cn from "classnames";
import { useTranslations } from "next-intl";

interface InputProps {
placeholder: string;
value?: string | number;
interface InputProps extends ComponentPropsWithRef<"input"> {
useTooltip?: boolean;
name: string;
type: string;
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
onBlur?: (event: React.ChangeEvent<HTMLInputElement>) => void;
ref?: React.RefObject<HTMLInputElement>;
focus?: boolean;
className?: string;
defaultValue?: string | number;
list?: string;
disabled?: boolean;
}

const Input = forwardRef<HTMLInputElement, InputProps>(
(
{
Expand Down Expand Up @@ -46,7 +34,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
setIsFocused(true);
};

const handleBlur = (event: React.ChangeEvent<HTMLInputElement>) => {
const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
setIsFocused(false);
if (onBlur) {
onBlur(event);
Expand Down
154 changes: 154 additions & 0 deletions src/components/elements/textarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { forwardRef, useEffect, useRef, useState } from "react";
import { useTranslations } from "next-intl";
import cn from "classnames";

interface TextAreaProps {
placeholder: string;
value?: string | number;
useTooltip?: boolean;
name: string;
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
onBlur?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
onKeyDown?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
ref?: React.RefObject<HTMLTextAreaElement>;
focus?: boolean;
className?: string;
defaultValue?: string | number;
disabled?: boolean;
maxRows?: number;
minRows?: number;
}

const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
(
{
value,
name,
onChange,
onBlur,
className = "",
defaultValue,
focus = false,
useTooltip = false,
maxRows = 5,
minRows = 1,
...rest
}: TextAreaProps,
forwardedRef,
) => {
const t = useTranslations("input");
const [isFocused, setIsFocused] = useState(false);
const textareaRef = useRef<HTMLTextAreaElement>(null);

const handleRef = (instance: HTMLTextAreaElement | null) => {
textareaRef.current = instance;
if (typeof forwardedRef === "function") {
forwardedRef(instance);
} else if (forwardedRef) {
forwardedRef.current = instance;
}
};

const adjustHeight = () => {
const textarea = textareaRef.current;
if (!textarea) return;

// Store the scroll position
const scrollTop = textarea.scrollTop;

// Reset height to calculate new height
textarea.style.height = "auto";

// Calculate based on content
const paddingTop = 2;
const paddingBottom = 2;
const border = 0; // Since we're using borderless style
const singleLineHeight = 24; // Height for single line

const minHeight = (singleLineHeight * minRows) + paddingTop + paddingBottom + border;
const maxHeight = (singleLineHeight * maxRows) + paddingTop + paddingBottom + border;
const contentHeight = textarea.scrollHeight;

// Ensure height is between minHeight and maxHeight
const newHeight = Math.min(Math.max(contentHeight, minHeight), maxHeight);


textarea.style.height = `${newHeight}px`;
textarea.style.overflowY = textarea.scrollHeight > newHeight ? "auto" : "hidden";

// Restore scroll position
textarea.scrollTop = scrollTop;
};

const handleFocus = () => {
setIsFocused(true);
};

const handleBlur = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
setIsFocused(false);
if (onBlur) {
onBlur(event);
}
};

const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
adjustHeight();
if (onChange) {
onChange(event);
}
};

useEffect(() => {
if (focus && forwardedRef && "current" in forwardedRef) {
forwardedRef.current?.focus();
}
}, [focus, forwardedRef]);

useEffect(() => {
if (defaultValue && forwardedRef && "current" in forwardedRef && onChange) {
const event = {
target: {
name: forwardedRef.current?.name || "",
value: defaultValue,
},
};
onChange(event as React.ChangeEvent<HTMLTextAreaElement>);
}
}, [defaultValue, onChange, forwardedRef]);

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
adjustHeight();
}, [value, defaultValue]);

return (
<div
className={cn("w-full", { tooltip: useTooltip && isFocused })}
data-tip={t("enterToSave")}
>
<textarea
name={name}
defaultValue={defaultValue}
value={value}
onChange={handleChange}
onBlur={handleBlur}
onFocus={handleFocus}
className={cn(
"input resize-none leading-tight",
"pt-1.5 pb-0.5",
"min-h-[24px]",
"transition-none",
className
)}
ref={handleRef}
rows={1}
{...rest}
/>
</div>
);
},
);

TextArea.displayName = "TextArea";

export default TextArea;
Loading

0 comments on commit f4c1eb3

Please sign in to comment.