From c8c252b43e0b6df5c16473c1141411b9b67180fc Mon Sep 17 00:00:00 2001 From: JeffDotPng Date: Tue, 23 Apr 2024 13:46:36 -0400 Subject: [PATCH 01/23] fix: update part variation id in form state when changing pages --- apps/web/src/components/unit/create-unit.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/src/components/unit/create-unit.tsx b/apps/web/src/components/unit/create-unit.tsx index dbc4d8cfd..87a2c75c2 100644 --- a/apps/web/src/components/unit/create-unit.tsx +++ b/apps/web/src/components/unit/create-unit.tsx @@ -123,6 +123,7 @@ const CreateUnit = ({ const devicePartVariations = getComponentPartVariationIds(partVariationTree); + form.setValue("partVariationId", partVariationId); form.setValue( "components", devicePartVariations.map(() => ({ From 84a5dc89c6effb822745354521cce410fa0c9e76 Mon Sep 17 00:00:00 2001 From: JeffDotPng Date: Tue, 23 Apr 2024 14:49:05 -0400 Subject: [PATCH 02/23] feat: use combobox for dropdowns instead --- .../src/components/project/new-project.tsx | 36 +++--- apps/web/src/components/ui/combobox.tsx | 108 +++++++++++------- .../components/unit/create-part-variation.tsx | 2 + apps/web/src/components/unit/create-unit.tsx | 2 + apps/web/src/components/unit/swap-unit.tsx | 57 +++++---- 5 files changed, 112 insertions(+), 93 deletions(-) diff --git a/apps/web/src/components/project/new-project.tsx b/apps/web/src/components/project/new-project.tsx index 6ed0fe49e..982f73cd6 100644 --- a/apps/web/src/components/project/new-project.tsx +++ b/apps/web/src/components/project/new-project.tsx @@ -48,6 +48,7 @@ import { useMutation } from "@tanstack/react-query"; import { client } from "@/lib/client"; import { useWorkspaceUser } from "@/hooks/use-workspace-user"; import { Info } from "lucide-react"; +import { Combobox } from "../ui/combobox"; type Props = { workspace: Workspace; @@ -172,28 +173,19 @@ export default function NewProjectButton({ workspace, partVariations }: Props) { Part Variation {partVariations.length > 0 ? ( - +
+ + form.setValue("partVariationId", val ?? "") + } + displaySelector={(p) => p.partNumber} + valueSelector={(p) => p.id} + descriptionSelector={(p) => p.description ?? ""} + searchText="Search part variation..." + /> +
) : (
No part variations found, go{" "} diff --git a/apps/web/src/components/ui/combobox.tsx b/apps/web/src/components/ui/combobox.tsx index 40fe2e2f3..2a142ba2e 100644 --- a/apps/web/src/components/ui/combobox.tsx +++ b/apps/web/src/components/ui/combobox.tsx @@ -16,6 +16,7 @@ import { } from "@/components/ui/popover"; import { useState } from "react"; import { CommandList } from "cmdk"; +import { ScrollArea } from "./scroll-area"; type Props = { options: T[]; @@ -26,6 +27,9 @@ type Props = { descriptionSelector?: (val: T) => string; placeholder?: string; searchText?: string; + avoidCollisions?: boolean; + side?: "top" | "bottom"; + disabled?: boolean; }; export function Combobox({ @@ -37,15 +41,62 @@ export function Combobox({ descriptionSelector, searchText, placeholder, + avoidCollisions, + side, + disabled = false, }: Props) { const [open, setOpen] = useState(false); const curValue = options.find((opt) => valueSelector(opt) === value); const label = curValue ? displaySelector(curValue) : placeholder; + const items = ( + + Nothing found. + + [data-radix-scroll-area-viewport]]:max-h-56"} + > + {options.map((opt) => ( + { + setValue(val); + setOpen(false); + }} + className="cursor-pointer" + keywords={[displaySelector(opt)]} + > + + {descriptionSelector ? ( +
+
+ {displaySelector(opt)} +
+
+ {descriptionSelector(opt)} +
+
+ ) : ( + displaySelector(opt) + )} +
+ ))} +
+
+
+ ); + const input = ; + return ( - - + + +
+ ))} + + + When you try to initialize an unit based on this part + variation, then you first need to make sure all its components + exist. + + + )} + + + + + + + + + + + ); +}; + +export default EditPartVariation; diff --git a/apps/web/src/routes/_protected/workspace/$namespace/part/$partId/index.tsx b/apps/web/src/routes/_protected/workspace/$namespace/part/$partId/index.tsx index 385bfbcf9..73a38e2a5 100644 --- a/apps/web/src/routes/_protected/workspace/$namespace/part/$partId/index.tsx +++ b/apps/web/src/routes/_protected/workspace/$namespace/part/$partId/index.tsx @@ -28,6 +28,9 @@ import { Separator } from "@/components/ui/separator"; import CreatePartVariation, { CreatePartVariationDefaultValues, } from "@/components/unit/create-part-variation"; +import EditPartVariation, { + EditPartVariationDefaultValues, +} from "@/components/unit/edit-part-variation"; import { PartVariationTreeVisualization } from "@/components/visualization/tree-visualization"; import { useWorkspaceUser } from "@/hooks/use-workspace-user"; import { client } from "@/lib/client"; @@ -52,7 +55,7 @@ import { } from "@tanstack/react-query"; import { Link, createFileRoute, useRouter } from "@tanstack/react-router"; import { ColumnDef } from "@tanstack/react-table"; -import { ArrowRight, MoreHorizontal, Plus } from "lucide-react"; +import { ArrowRight, MoreHorizontal, Pencil, Plus } from "lucide-react"; import { useCallback, useMemo, useState } from "react"; import { toast } from "sonner"; @@ -81,13 +84,14 @@ export const Route = createFileRoute( const partVariationColumns: ( openCreateDialog: (variant: PartVariation) => Promise, + openEditDialog: (variant: PartVariation) => Promise, ) => ColumnDef< PartVariation & { unitCount: number; market?: PartVariationMarket | null; type?: PartVariationType | null; } ->[] = (openCreateDialog: (variant: PartVariation) => Promise) => [ +>[] = (openCreateDialog, openEditDialog) => [ { accessorKey: "name", header: "Part Number", @@ -154,6 +158,16 @@ const partVariationColumns: ( Copy ID + { + openEditDialog(partVariant); + }} + > +
+ +
Edit
+
+
{ openCreateDialog(partVariant); @@ -213,6 +227,7 @@ function PartPage() { const { partId } = Route.useParams(); const [createOpen, setCreateOpen] = useState(false); + const [editOpen, setEditOpen] = useState(false); const router = useRouter(); const queryClient = useQueryClient(); @@ -257,10 +272,17 @@ function PartPage() { enabled: selectedPartVariationId !== undefined, }); - const [defaultValues, setDefaultValues] = useState< + const [createDefaultValues, setCreateDefaultValues] = useState< CreatePartVariationDefaultValues | undefined >(); + const [editingPartVariationId, setEditingPartVariationId] = useState< + string | undefined + >(); + const [editDefaultValues, setEditDefaultValues] = useState< + EditPartVariationDefaultValues | undefined + >(); + const openCreateDialog = useCallback( async (variant?: PartVariation) => { if (variant) { @@ -270,8 +292,10 @@ function PartPage() { context: { workspace }, }), ); - setDefaultValues({ + setCreateDefaultValues({ partNumber: removePrefix(tree.partNumber, part.name + "-"), + type: tree.type?.name, + market: tree.market?.name, hasComponents: tree.components.length > 0, components: tree.components.map((c) => ({ count: c.count, @@ -280,7 +304,7 @@ function PartPage() { description: tree.description ?? undefined, }); } else { - setDefaultValues(undefined); + setCreateDefaultValues(undefined); } setCreateOpen(true); @@ -288,9 +312,35 @@ function PartPage() { [queryClient, workspace, part.name], ); + const openEditDialog = useCallback( + async (variant: PartVariation) => { + const tree = await queryClient.ensureQueryData( + getPartVariationQueryOpts({ + partVariationId: variant.id, + context: { workspace }, + }), + ); + setEditingPartVariationId(variant.id); + setEditDefaultValues({ + partNumber: removePrefix(tree.partNumber, part.name + "-"), + type: tree.type?.name, + market: tree.market?.name, + hasComponents: tree.components.length > 0, + components: tree.components.map((c) => ({ + count: c.count, + partVariationId: c.partVariation.id, + })), + description: tree.description ?? undefined, + }); + + setEditOpen(true); + }, + [queryClient, workspace, part.name], + ); + const columns = useMemo( - () => partVariationColumns(openCreateDialog), - [openCreateDialog], + () => partVariationColumns(openCreateDialog, openEditDialog), + [openCreateDialog, openEditDialog], ); return ( @@ -345,13 +395,31 @@ function PartPage() { open={createOpen} setOpen={setCreateOpen} openDialog={openCreateDialog} - defaultValues={defaultValues} - setDefaultValues={setDefaultValues} + defaultValues={createDefaultValues} + setDefaultValues={setCreateDefaultValues} partVariationTypes={partVariationTypes} partVariationMarkets={partVariationMarkets} /> )} + {workspaceUserPerm.canWrite() && + editDefaultValues && + editingPartVariationId && ( + <> +
+ + + )}

Part Variations

diff --git a/packages/shared/src/types/part-variation.ts b/packages/shared/src/types/part-variation.ts index a16bb49d5..3280e3ce2 100644 --- a/packages/shared/src/types/part-variation.ts +++ b/packages/shared/src/types/part-variation.ts @@ -1,7 +1,13 @@ import { t, Static } from "elysia"; -import { PartVariation } from "../schemas/public/PartVariation"; -export type { PartVariation }; +import { PartVariation as SchemaPartVariation } from "../schemas/public/PartVariation"; +import { PartVariationMarket } from "../schemas/public/PartVariationMarket"; +import { PartVariationType } from "../schemas/public/PartVariationType"; + +export type PartVariation = SchemaPartVariation & { + type?: PartVariationType | null; + market?: PartVariationMarket | null; +}; export const partVariationComponent = t.Object({ partVariationId: t.String(), From b8d6b17fab0cec4b41e8d8531dee318e7e96be1c Mon Sep 17 00:00:00 2001 From: Joey Yu Date: Tue, 23 Apr 2024 15:03:24 -0400 Subject: [PATCH 09/23] fix: forgot to add deleted_record to migration --- .../migrations/0020_deleted_record_insert.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/server/migrations/0020_deleted_record_insert.ts b/apps/server/migrations/0020_deleted_record_insert.ts index 639b3897a..4077edcf6 100644 --- a/apps/server/migrations/0020_deleted_record_insert.ts +++ b/apps/server/migrations/0020_deleted_record_insert.ts @@ -30,13 +30,23 @@ const tables = [ ]; export async function up(db: Kysely): Promise { + await db.schema + .createTable("deleted_record") + .addColumn("id", "serial", (col) => col.primaryKey()) + .addColumn("data", "jsonb") + .addColumn("deleted_at", "timestamptz", (col) => + col.defaultTo(sql`now()`).notNull(), + ) + .addColumn("table_name", "text") + .execute(); + await sql` CREATE FUNCTION deleted_record_insert() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN - EXECUTE 'INSERT INTO deleted_record (data, object_id, table_name) VALUES ($1, $2, $3)' - USING to_jsonb(OLD.*), OLD.id, TG_TABLE_NAME; + EXECUTE 'INSERT INTO deleted_record (data, table_name) VALUES ($1, $2)' + USING to_jsonb(OLD.*), TG_TABLE_NAME; RETURN OLD; END; From 5f4e77bb30e4b1f84024603bfd41353347f78ac9 Mon Sep 17 00:00:00 2001 From: Joey Yu Date: Tue, 23 Apr 2024 15:10:25 -0400 Subject: [PATCH 10/23] fix: make sure to drop table --- apps/server/migrations/0020_deleted_record_insert.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/server/migrations/0020_deleted_record_insert.ts b/apps/server/migrations/0020_deleted_record_insert.ts index 4077edcf6..8f6f5b79c 100644 --- a/apps/server/migrations/0020_deleted_record_insert.ts +++ b/apps/server/migrations/0020_deleted_record_insert.ts @@ -67,5 +67,6 @@ export async function down(db: Kysely): Promise { db, ); } + await db.schema.dropTable("deleted_record").execute(); await sql`DROP FUNCTION deleted_record_insert`.execute(db); } From 8cfdbab3806a1f83394faffc8eb349b726039e74 Mon Sep 17 00:00:00 2001 From: JeffDotPng Date: Wed, 24 Apr 2024 15:44:54 -0400 Subject: [PATCH 11/23] chore: rename some stuff --- apps/server/src/db/part-variation.ts | 82 ++++++++++--------- apps/server/src/routes/part-variation.ts | 18 +++- .../components/unit/edit-part-variation.tsx | 6 +- packages/shared/src/types/part-variation.ts | 4 +- 4 files changed, 62 insertions(+), 48 deletions(-) diff --git a/apps/server/src/db/part-variation.ts b/apps/server/src/db/part-variation.ts index 2bf537804..8211715b5 100644 --- a/apps/server/src/db/part-variation.ts +++ b/apps/server/src/db/part-variation.ts @@ -5,7 +5,7 @@ import { PartVariation, PartVariationTreeNode, PartVariationTreeRoot, - UpdatePartVariation, + PartVariationUpdate, } from "@cloud/shared"; import { ExpressionBuilder, Kysely, sql } from "kysely"; import { Result, err, ok, safeTry } from "neverthrow"; @@ -378,7 +378,7 @@ export async function updatePartVariation( db: Kysely, partVariationId: string, workspaceId: string, - update: UpdatePartVariation, + update: PartVariationUpdate, ) { const { components, type: typeName, market: marketName, ...data } = update; @@ -427,21 +427,19 @@ export async function updatePartVariation( marketId = market.id; } - yield* ( - await tryQuery( - tx - .updateTable("part_variation") - .set({ - ...data, - partNumber, - typeId, - marketId, - }) - .where("id", "=", partVariationId) - .executeTakeFirstOrThrow( - () => new InternalServerError("Failed to create part variation"), - ), - ) + yield* tryQuery( + tx + .updateTable("part_variation") + .set({ + ...data, + partNumber, + typeId, + marketId, + }) + .where("id", "=", partVariationId) + .executeTakeFirstOrThrow( + () => new InternalServerError("Failed to create part variation"), + ), ).safeUnwrap(); const updatedPartVariation = await getPartVariation( @@ -457,33 +455,37 @@ export async function updatePartVariation( } // Rebuild this level of the component tree - await tx - .deleteFrom("part_variation_relation") - .where("parentPartVariationId", "=", partVariationId) - .execute(); + yield* tryQuery( + tx + .deleteFrom("part_variation_relation") + .where("parentPartVariationId", "=", partVariationId) + .execute(), + ).safeUnwrap(); if (components.length > 0) { - await tx - .insertInto("part_variation_relation") - .values( - components.map((c) => ({ - parentPartVariationId: updatedPartVariation.id, - childPartVariationId: c.partVariationId, - workspaceId, - count: c.count, - })), - ) - .execute(); + yield* tryQuery( + tx + .insertInto("part_variation_relation") + .values( + components.map((c) => ({ + parentPartVariationId: updatedPartVariation.id, + childPartVariationId: c.partVariationId, + workspaceId, + count: c.count, + })), + ) + .execute(), + ).safeUnwrap(); const graph = await getPartVariationTree(tx, updatedPartVariation); - const cycleRes = detectCycle(graph); - if (cycleRes.isErr()) { - return err( - new BadRequestError( - `Cycle detected in component graph at: ${cycleRes.error.partNumber}, not allowed`, - ), - ); - } + yield* detectCycle(graph) + .mapErr( + (e) => + new BadRequestError( + `Cycle detected in component graph at: ${e.partNumber}, not allowed`, + ), + ) + .safeUnwrap(); } return ok(undefined); diff --git a/apps/server/src/routes/part-variation.ts b/apps/server/src/routes/part-variation.ts index ef17c01fb..848527c7e 100644 --- a/apps/server/src/routes/part-variation.ts +++ b/apps/server/src/routes/part-variation.ts @@ -1,4 +1,4 @@ -import { insertPartVariation } from "@cloud/shared"; +import { insertPartVariation, partVariationUpdate } from "@cloud/shared"; import Elysia, { t } from "elysia"; import { db } from "../db/kysely"; import { @@ -6,6 +6,7 @@ import { getPartVariation, getPartVariationTree, getPartVariationUnits, + updatePartVariation, withPartVariationMarket, withPartVariationType, } from "../db/part-variation"; @@ -99,17 +100,28 @@ export const PartVariationRoute = new Elysia({ ) .patch( "/", - async ({ workspace, params: { partVariationId }, error }) => { + async ({ workspace, body, params: { partVariationId }, error }) => { const partVariation = await getPartVariation( workspace.id, partVariationId, ); if (partVariation === undefined) return error(404, "Part variation not found"); + + const res = await updatePartVariation( + db, + partVariationId, + workspace.id, + body, + ); + + if (res.isErr()) return error(res.error.code, res.error); + + return res.value; }, { params: t.Object({ partVariationId: t.String() }), - body: t.Object({}), + body: partVariationUpdate, async beforeHandle({ workspaceUser, error }) { const perm = await checkWorkspacePerm({ workspaceUser }); diff --git a/apps/web/src/components/unit/edit-part-variation.tsx b/apps/web/src/components/unit/edit-part-variation.tsx index cdb611d45..1b3bf988b 100644 --- a/apps/web/src/components/unit/edit-part-variation.tsx +++ b/apps/web/src/components/unit/edit-part-variation.tsx @@ -24,7 +24,7 @@ import { getPartVariationsQueryKey, } from "@/lib/queries/part-variation"; import { handleError } from "@/lib/utils"; -import { Part, PartVariation, updatePartVariation } from "@cloud/shared"; +import { Part, PartVariation, partVariationUpdate } from "@cloud/shared"; import { PartVariationMarket } from "@cloud/shared/src/schemas/public/PartVariationMarket"; import { PartVariationType } from "@cloud/shared/src/schemas/public/PartVariationType"; import { typeboxResolver } from "@hookform/resolvers/typebox"; @@ -39,7 +39,7 @@ import { Combobox } from "../ui/combobox"; import { useEffect } from "react"; const partVariationFormSchema = t.Composite([ - updatePartVariation, + partVariationUpdate, t.Object({ hasComponents: t.Boolean() }), ]); @@ -76,7 +76,7 @@ const EditPartVariation = ({ const queryClient = useQueryClient(); const editPartVariation = useMutation({ - mutationFn: async (values: Static) => { + mutationFn: async (values: Static) => { const { error } = await client .partVariation({ partVariationId }) .index.patch(values, { diff --git a/packages/shared/src/types/part-variation.ts b/packages/shared/src/types/part-variation.ts index 3280e3ce2..644435ae2 100644 --- a/packages/shared/src/types/part-variation.ts +++ b/packages/shared/src/types/part-variation.ts @@ -29,7 +29,7 @@ export const insertPartVariation = t.Object({ export type InsertPartVariation = Static; -export const updatePartVariation = t.Object({ +export const partVariationUpdate = t.Object({ type: t.Optional(t.String()), market: t.Optional(t.String()), partNumber: t.String({ minLength: 1 }), @@ -39,7 +39,7 @@ export const updatePartVariation = t.Object({ }), }); -export type UpdatePartVariation = Static; +export type PartVariationUpdate = Static; export type PartVariationTreeRoot = PartVariation & { components: { count: number; partVariation: PartVariationTreeNode }[]; From 0605562e0e2bc965366ef6a8af7743acfa36e2a7 Mon Sep 17 00:00:00 2001 From: JeffDotPng Date: Wed, 24 Apr 2024 16:57:20 -0400 Subject: [PATCH 12/23] feat: it works --- apps/server/src/db/part-variation.ts | 19 ++++++------- apps/server/src/routes/part-variation.ts | 2 +- .../components/unit/create-part-variation.tsx | 2 +- .../components/unit/edit-part-variation.tsx | 28 +++++++++---------- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/apps/server/src/db/part-variation.ts b/apps/server/src/db/part-variation.ts index 8211715b5..87ce37bc3 100644 --- a/apps/server/src/db/part-variation.ts +++ b/apps/server/src/db/part-variation.ts @@ -210,7 +210,7 @@ type PartVariationEdge = { parentPartVariationId: string; count: number; description: string | null; - depth: number; + // depth: number; }; async function getPartVariationTreeEdges( @@ -232,10 +232,10 @@ async function getPartVariationTreeEdges( "childPartVariationId as partVariationId", "part_variation.partNumber", "part_variation.description", - sql`1`.as("depth"), + // sql`1`.as("depth"), ]) .where("parentPartVariationId", "=", partVariation.id) - .unionAll((eb) => + .union((eb) => eb .selectFrom("part_variation_relation as mr") .innerJoin( @@ -254,13 +254,13 @@ async function getPartVariationTreeEdges( "mr.childPartVariationId as partVariationId", "part_variation.partNumber", "part_variation.description", - sql`depth + 1`.as("depth"), + // sql`depth + 1`.as("depth"), ]), ), ) .selectFrom("part_variation_tree") .selectAll() - .distinctOn(["parentPartVariationId", "partVariationId"]) + // .distinctOn(["parentPartVariationId", "partVariationId"]) .execute(); } @@ -271,7 +271,8 @@ export async function getPartVariationTree( const edges = await getPartVariationTreeEdges(db, partVariation); return buildPartVariationTree( partVariation, - _.sortBy(edges, (e) => e.depth), + // _.sortBy(edges, (e) => e.depth), + edges, ); } @@ -343,7 +344,7 @@ async function haveComponentsChanged( const before = makeObject(curComponents); const after = makeObject(components); - return _.isEqual(before, after); + return !_.isEqual(before, after); } // Returns an error with the node of the cycle if one is detected, otherwise ok @@ -437,9 +438,7 @@ export async function updatePartVariation( marketId, }) .where("id", "=", partVariationId) - .executeTakeFirstOrThrow( - () => new InternalServerError("Failed to create part variation"), - ), + .execute(), ).safeUnwrap(); const updatedPartVariation = await getPartVariation( diff --git a/apps/server/src/routes/part-variation.ts b/apps/server/src/routes/part-variation.ts index 848527c7e..ae02b64b1 100644 --- a/apps/server/src/routes/part-variation.ts +++ b/apps/server/src/routes/part-variation.ts @@ -117,7 +117,7 @@ export const PartVariationRoute = new Elysia({ if (res.isErr()) return error(res.error.code, res.error); - return res.value; + return {}; }, { params: t.Object({ partVariationId: t.String() }), diff --git a/apps/web/src/components/unit/create-part-variation.tsx b/apps/web/src/components/unit/create-part-variation.tsx index a4ad64556..4b0809334 100644 --- a/apps/web/src/components/unit/create-part-variation.tsx +++ b/apps/web/src/components/unit/create-part-variation.tsx @@ -334,7 +334,7 @@ const CreatePartVariation = ({ 'blueprint' of the system. {fields.map((field, index) => ( -
+
{ queryClient.invalidateQueries({ queryKey: getPartVariationsQueryKey() }); + queryClient.invalidateQueries({ + queryKey: getPartVariationQueryKey(partVariationId), + }); queryClient.invalidateQueries({ queryKey: getPartPartVariationsQueryKey(part.id), }); @@ -99,24 +103,20 @@ const EditPartVariation = ({ defaultValues, }); - useEffect(() => { - if (open) { - form.reset(defaultValues); - } - }, [open, defaultValues, form]); - const { fields, insert, remove } = useFieldArray({ control: form.control, name: "components", keyName: "partVariationId", }); - const handleRemove = (i: number) => { - if (fields.length === 1) { - return; + useEffect(() => { + if (open) { + form.reset(defaultValues); + } else { + // This is required because react hook form is stupid + form.reset({ ...defaultValues, components: [] }); } - remove(i); - }; + }, [open, defaultValues, form]); const partNamePrefix = part.name + "-"; @@ -131,8 +131,8 @@ const EditPartVariation = ({ components: [], }), { - loading: "Creating your part variation...", - success: "Part variation created.", + loading: "Updating your part variation...", + success: "Part variation updated.", error: handleError, }, ); @@ -348,7 +348,7 @@ const EditPartVariation = ({ /> +
+ ))} + + When you try to initialize an unit based on this part + variation, then you first need to make sure all its + components exist. +
- ))} - - - When you try to initialize an unit based on this part - variation, then you first need to make sure all its components - exist. - -
+ )} + )} diff --git a/apps/web/src/routes/_protected/workspace/$namespace/part/$partId/index.tsx b/apps/web/src/routes/_protected/workspace/$namespace/part/$partId/index.tsx index b27df7b08..104b71150 100644 --- a/apps/web/src/routes/_protected/workspace/$namespace/part/$partId/index.tsx +++ b/apps/web/src/routes/_protected/workspace/$namespace/part/$partId/index.tsx @@ -41,6 +41,7 @@ import { getPartVariationTypesQueryOpts, getPartVariationsQueryOpts, } from "@/lib/queries/part-variation"; +import { getPartVariationUnitQueryOpts } from "@/lib/queries/unit"; import { removePrefix } from "@/lib/string"; import { Route as WorkspaceIndexRoute } from "@/routes/_protected/workspace/$namespace"; import { @@ -284,6 +285,10 @@ function PartPage() { const [editingPartVariation, setEditingPartVariation] = useState< PartVariationTreeRoot | undefined >(); + const [ + editingPartVariationHasExistingUnits, + setEditingPartVariationHasExistingUnits, + ] = useState(false); const openCreateDialog = useCallback( async (variant?: PartVariation) => { @@ -322,7 +327,14 @@ function PartPage() { context: { workspace }, }), ); + const existingUnits = await queryClient.ensureQueryData( + getPartVariationUnitQueryOpts({ + partVariationId: variant.id, + context: { workspace }, + }), + ); setEditingPartVariation(tree); + setEditingPartVariationHasExistingUnits(existingUnits.length > 0); setEditOpen(true); }, [queryClient, workspace], @@ -405,6 +417,7 @@ function PartPage() { Date: Fri, 26 Apr 2024 11:19:44 -0400 Subject: [PATCH 18/23] chore: remove unused --- apps/server/src/db/part-variation.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/apps/server/src/db/part-variation.ts b/apps/server/src/db/part-variation.ts index 8ca010ec7..c34b7bf95 100644 --- a/apps/server/src/db/part-variation.ts +++ b/apps/server/src/db/part-variation.ts @@ -215,7 +215,6 @@ type PartVariationEdge = { parentPartVariationId: string; count: number; description: string | null; - // depth: number; }; async function getPartVariationTreeEdges( @@ -237,7 +236,6 @@ async function getPartVariationTreeEdges( "childPartVariationId as partVariationId", "part_variation.partNumber", "part_variation.description", - // sql`1`.as("depth"), ]) .where("parentPartVariationId", "=", partVariation.id) .union((eb) => @@ -259,13 +257,11 @@ async function getPartVariationTreeEdges( "mr.childPartVariationId as partVariationId", "part_variation.partNumber", "part_variation.description", - // sql`depth + 1`.as("depth"), ]), ), ) .selectFrom("part_variation_tree") .selectAll() - // .distinctOn(["parentPartVariationId", "partVariationId"]) .execute(); } @@ -274,11 +270,7 @@ export async function getPartVariationTree( partVariation: PartVariation, ): Promise { const edges = await getPartVariationTreeEdges(db, partVariation); - return buildPartVariationTree( - partVariation, - // _.sortBy(edges, (e) => e.depth), - edges, - ); + return buildPartVariationTree(partVariation, edges); } function buildPartVariationTree( From c65484d60f4480b202e81f3ce7dc70031a6f9b8a Mon Sep 17 00:00:00 2001 From: Jeff Zhang <47371088+39bytes@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:37:59 -0400 Subject: [PATCH 19/23] fix: create -> update Co-authored-by: Gui --- apps/web/src/components/unit/edit-part-variation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/unit/edit-part-variation.tsx b/apps/web/src/components/unit/edit-part-variation.tsx index 67858e0e4..ec8b8a0a3 100644 --- a/apps/web/src/components/unit/edit-part-variation.tsx +++ b/apps/web/src/components/unit/edit-part-variation.tsx @@ -381,7 +381,7 @@ const EditPartVariation = ({ Close - + From 5ce12b4abf32b93597381b8b116399f477d72431 Mon Sep 17 00:00:00 2001 From: Jeff Zhang <47371088+39bytes@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:38:16 -0400 Subject: [PATCH 20/23] fix: relative import -> absolute Co-authored-by: Gui --- apps/web/src/components/project/new-project.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/project/new-project.tsx b/apps/web/src/components/project/new-project.tsx index 829277945..2b421ae0a 100644 --- a/apps/web/src/components/project/new-project.tsx +++ b/apps/web/src/components/project/new-project.tsx @@ -41,7 +41,7 @@ import { typeboxResolver } from "@hookform/resolvers/typebox"; import { useMutation } from "@tanstack/react-query"; import { Link, useRouter } from "@tanstack/react-router"; import { Info } from "lucide-react"; -import { Combobox } from "../ui/combobox"; +import { Combobox } from "@/components/ui/combobox"; type Props = { workspace: Workspace; From b3f03e08dc2b2f7a16978e30907eb39dd1210cec Mon Sep 17 00:00:00 2001 From: Jeff Zhang <47371088+39bytes@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:38:28 -0400 Subject: [PATCH 21/23] fix: relative import -> absolute Co-authored-by: Gui --- apps/web/src/components/unit/swap-unit.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/unit/swap-unit.tsx b/apps/web/src/components/unit/swap-unit.tsx index 7c137acbb..42b1d56f2 100644 --- a/apps/web/src/components/unit/swap-unit.tsx +++ b/apps/web/src/components/unit/swap-unit.tsx @@ -37,7 +37,7 @@ import { import { typeboxResolver } from "@hookform/resolvers/typebox"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Edit } from "lucide-react"; -import { Combobox } from "../ui/combobox"; +import { Combobox } from "@/components/ui/combobox"; type FormSchema = SwapUnitComponent; From a7c3974e7c5b001b721e6a4687a5554edbbacd8a Mon Sep 17 00:00:00 2001 From: JeffDotPng Date: Mon, 29 Apr 2024 15:41:35 -0400 Subject: [PATCH 22/23] chore: update error text --- apps/server/src/db/part-variation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/db/part-variation.ts b/apps/server/src/db/part-variation.ts index c34b7bf95..3420fe3c0 100644 --- a/apps/server/src/db/part-variation.ts +++ b/apps/server/src/db/part-variation.ts @@ -478,7 +478,7 @@ export async function updatePartVariation( .mapErr( (e) => new BadRequestError( - `Cycle detected in component graph at: ${e.partNumber}, not allowed`, + `Cyclic reference detected in component hierarchy at: ${e.partNumber}, not allowed`, ), ) .safeUnwrap(); From 3e2283114a34363c38f02d41691d20e130aa2a66 Mon Sep 17 00:00:00 2001 From: JeffDotPng Date: Tue, 30 Apr 2024 15:05:31 -0400 Subject: [PATCH 23/23] fix: put table back --- apps/server/migrations/0020_deleted_record_insert.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/server/migrations/0020_deleted_record_insert.ts b/apps/server/migrations/0020_deleted_record_insert.ts index 8f6f5b79c..e0858c204 100644 --- a/apps/server/migrations/0020_deleted_record_insert.ts +++ b/apps/server/migrations/0020_deleted_record_insert.ts @@ -22,6 +22,7 @@ const tables = [ "tag", "test", "unit", + "unit_relation", "unit_revision", "user", "user_invite",