Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: search in each dropdown form #106

Open
wants to merge 9 commits into
base: staging
Choose a base branch
from
1 change: 0 additions & 1 deletion apps/server/migrations/0020_deleted_record_insert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ const tables = [
"unit_revision",
"user",
"user_invite",
// "user_session",
"workspace",
"workspace_user",
];
Expand Down
134 changes: 73 additions & 61 deletions apps/server/src/db/unit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { generateDatabaseId } from "../lib/db-utils";
import { fromTransaction, generateDatabaseId, tryQuery } from "../lib/db-utils";
import {
BadRequestError,
DuplicateError,
Expand All @@ -20,16 +20,17 @@ import { ExpressionBuilder, Kysely } from "kysely";
import { jsonObjectFrom } from "kysely/helpers/postgres";
import _ from "lodash";
import { User } from "lucia";
import { fromPromise } from "neverthrow";
import { Result, err, fromPromise, ok, safeTry } from "neverthrow";
import { db } from "./kysely";
import { getPartVariation, getPartVariationComponents } from "./part-variation";
import { markUpdatedAt } from "./query";

export async function getUnit(id: string) {
export async function getUnit(unitId: string, workspaceId: string) {
return await db
.selectFrom("unit")
.selectAll()
.where("unit.id", "=", id)
.where("unit.id", "=", unitId)
.where("workspaceId", "=", workspaceId)
.executeTakeFirst();
}

Expand Down Expand Up @@ -165,78 +166,89 @@ export async function doUnitComponentSwap(
unit: Unit,
user: WorkspaceUser,
input: SwapUnitComponent,
) {
): Promise<Result<void, RouteError>> {
const unitComponents = await getUnitComponentsWithPartVariation(unit.id);

return fromPromise(
db.transaction().execute(async (tx) => {
const oldUnitComponent = await tx
.selectFrom("unit")
.selectAll()
.where("unit.id", "=", input.oldUnitComponentId)
.where("unit.workspaceId", "=", user.workspaceId)
.executeTakeFirstOrThrow(
() => new NotFoundError("Old component not found"),
);
return fromTransaction(async (tx) => {
return safeTry(async function* () {
const oldUnitComponent = await getUnit(
input.oldUnitComponentId,
user.workspaceId,
);
if (oldUnitComponent === undefined)
return err(new NotFoundError("Old component not found"));

const newUnitComponent = await tx
.selectFrom("unit")
.selectAll()
.where("unit.id", "=", input.newUnitComponentId)
.where("unit.workspaceId", "=", user.workspaceId)
.executeTakeFirstOrThrow(
() => new NotFoundError("New component not found"),
);
const newUnitComponent = await getUnit(
input.newUnitComponentId,
user.workspaceId,
);
if (newUnitComponent === undefined)
return err(new NotFoundError("New component not found"));

if (
oldUnitComponent.partVariationId !== newUnitComponent.partVariationId
) {
throw new BadRequestError("PartVariation mismatch");
return err(new BadRequestError("PartVariation mismatch"));
}

if (
!unitComponents.some((hc) => hc.unitId === input.oldUnitComponentId)
) {
throw new BadRequestError("Old component is not a part of the unit");
return err(
new BadRequestError("Old component is not a part of the unit"),
);
}

await tx
.deleteFrom("unit_relation as hr")
.where("hr.parentUnitId", "=", unit.id)
.where("hr.childUnitId", "=", input.oldUnitComponentId)
.execute();
yield* (
await tryQuery(
tx
.deleteFrom("unit_relation as hr")
.where("hr.parentUnitId", "=", unit.id)
.where("hr.childUnitId", "=", input.oldUnitComponentId)
.execute(),
)
).safeUnwrap();

yield* (
await tryQuery(
tx
.insertInto("unit_relation")
.values({
parentUnitId: unit.id,
childUnitId: input.newUnitComponentId,
workspaceId: unit.workspaceId,
})
.execute(),
)
).safeUnwrap();

yield* (
await tryQuery(
tx
.insertInto("unit_revision")
.values([
{
revisionType: "remove",
userId: user.userId,
unitId: unit.id,
componentId: input.oldUnitComponentId,
reason: input.reason ?? "Component swap",
},
{
revisionType: "add",
userId: user.userId,
unitId: unit.id,
componentId: input.newUnitComponentId,
reason: input.reason ?? "Component swap",
},
])
.execute(),
)
).safeUnwrap();

await tx
.insertInto("unit_relation")
.values({
parentUnitId: unit.id,
childUnitId: input.newUnitComponentId,
workspaceId: unit.workspaceId,
})
.execute();

await tx
.insertInto("unit_revision")
.values([
{
revisionType: "remove",
userId: user.userId,
unitId: unit.id,
componentId: input.oldUnitComponentId,
reason: input.reason ?? "Component swap",
},
{
revisionType: "add",
userId: user.userId,
unitId: unit.id,
componentId: input.newUnitComponentId,
reason: input.reason ?? "Component swap",
},
])
.execute();
}),
(e) => e as RouteError,
);
return ok(undefined);
});
});
}

export const notInUse = ({
Expand Down
6 changes: 3 additions & 3 deletions apps/server/src/routes/unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export const UnitRoute = new Elysia({ prefix: "/unit", name: "UnitRoute" })
.patch(
"/",
async ({ workspaceUser, body, error, params: { unitId } }) => {
const unit = await getUnit(unitId);
const unit = await getUnit(unitId, workspaceUser.workspaceId);
if (!unit) return error("Not Found");

const res = await doUnitComponentSwap(unit, workspaceUser, body);
Expand All @@ -134,8 +134,8 @@ export const UnitRoute = new Elysia({ prefix: "/unit", name: "UnitRoute" })
)
.get(
"/revisions",
async ({ params: { unitId } }) => {
const unit = await getUnit(unitId);
async ({ workspaceUser, params: { unitId } }) => {
const unit = await getUnit(unitId, workspaceUser.workspaceId);
if (!unit) return error("Not Found");
return await getUnitRevisions(unit.id);
},
Expand Down
55 changes: 20 additions & 35 deletions apps/web/src/components/project/new-project.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { toast } from "sonner";

import {
Form,
Expand All @@ -32,22 +32,16 @@ import {
} from "@/components/ui/dialog";

import { Input } from "@/components/ui/input";
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "@/components/ui/select";
import { handleError } from "@/lib/utils";

import { Workspace, CreateProjectSchema, PartVariation } from "@cloud/shared";
import { useWorkspaceUser } from "@/hooks/use-workspace-user";
import { client } from "@/lib/client";
import { CreateProjectSchema, PartVariation, Workspace } from "@cloud/shared";
import { typeboxResolver } from "@hookform/resolvers/typebox";
import { Link, useRouter } from "@tanstack/react-router";
import { useMutation } from "@tanstack/react-query";
import { client } from "@/lib/client";
import { useWorkspaceUser } from "@/hooks/use-workspace-user";
import { Link, useRouter } from "@tanstack/react-router";
import { Info } from "lucide-react";
import { Combobox } from "@/components/ui/combobox";

type Props = {
workspace: Workspace;
Expand Down Expand Up @@ -172,28 +166,19 @@ export default function NewProjectButton({ workspace, partVariations }: Props) {
<FormLabel>Part Variation</FormLabel>
<FormControl>
{partVariations.length > 0 ? (
<Select
value={field.value}
onValueChange={field.onChange}
>
<SelectTrigger className="w-[200px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
{partVariations.map((partVariation) => (
<SelectItem
value={partVariation.id}
key={partVariation.id}
>
{partVariation.partNumber}
{/* TODO: display partVariation type */}
{/* <Badge className="ml-2" variant="outline"> */}
{/* {partVariation.type} */}
{/* </Badge> */}
</SelectItem>
))}
</SelectContent>
</Select>
<div>
<Combobox
options={partVariations}
value={field.value}
setValue={(val) =>
form.setValue("partVariationId", val ?? "")
}
displaySelector={(p) => p.partNumber}
valueSelector={(p) => p.id}
descriptionSelector={(p) => p.description ?? ""}
searchText="Search part variation..."
/>
</div>
) : (
<div className="text-sm">
No part variations found, go{" "}
Expand Down
Loading
Loading