Skip to content

Commit

Permalink
Merge pull request #51 from joshtyf/feature/implement-service-catalog…
Browse files Browse the repository at this point in the history
…-sub-pages

Implement service catalog sub pages
  • Loading branch information
Ziyang-98 authored Feb 3, 2024
2 parents 41d89e4 + 432980b commit ac627b5
Show file tree
Hide file tree
Showing 22 changed files with 1,526 additions and 56 deletions.
1 change: 1 addition & 0 deletions frontend/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ module.exports = {
"@typescript-eslint/semi": "off",
"@typescript-eslint/indent": "off",
"@typescript-eslint/comma-dangle": "off",
"@typescript-eslint/no-unused-vars": "off",
},
}
290 changes: 283 additions & 7 deletions frontend/package-lock.json

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-tooltip": "^1.0.7",
"@rjsf/core": "^5.16.1",
"@rjsf/utils": "^5.16.1",
"@rjsf/validator-ajv8": "^5.16.1",
"@tailwindcss/typography": "^0.5.10",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
Expand All @@ -38,6 +42,12 @@
"tailwindcss-animate": "^1.0.7",
"zod": "^3.22.4"
},
"overrides": {
"@microlink/react-json-view": {
"react": "$react",
"react-dom": "$react-dom"
}
},
"devDependencies": {
"@babel/eslint-parser": "^7.23.3",
"@types/node": "^20",
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/app/(authenticated)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export default function AuthenticatedLayout({
/>
<div className="w-full">
<Navbar toggleSidebar={toggleSidebar} />
{children}
<div className="w-full h-full max-h-[90vh] flex justify-center items-center flex-col relative">
<div className="w-5/6 h-full relative">{children}</div>
</div>
</div>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ServiceRequest } from "@/types/service"
import { IChangeEvent } from "@rjsf/core"
import { RJSFSchema } from "@rjsf/utils"

interface UseServiceRequestProps {
serviceRequestId: string
}

const useServiceRequest = ({ serviceRequestId }: UseServiceRequestProps) => {
const serviceRequest: ServiceRequest = {
name: "Sample Service Request",
description: "Sample Service Request Form",
form: {
input: {
title: "Input",
type: "input",
description: "Input Description with minimum length 1",
minLength: 1,
required: true,
},
select: {
title: "Select Option",
type: "select",
description: "Dropdown selection with default value as Item 1",
options: ["Item 1", "Item 2", "Item 3"],
required: true,
},
checkboxes: {
title: "Checkboxes",
type: "checkboxes",
description: "You can select more than 1 item",
options: ["Item 1", "Item 2", "Item 3"],
required: false,
},
},
}

const handleSubmit = (data: IChangeEvent<object, RJSFSchema, object>) => {
// TODO: Replace with API call
// TODO: Add validations
console.log(
"Data submitted: ",
"Service id: " + serviceRequestId,
data.formData
)
}
return {
serviceRequest,
handleSubmit,
}
}

export default useServiceRequest
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"use client"

import React from "react"
import { useParams, useRouter } from "next/navigation"
import HeaderAccessory from "@/components/ui/header-accessory"
import { Button } from "@/components/ui/button"
import { ChevronLeft } from "lucide-react"
import useServiceRequest from "./_hooks/use-service-request"
import validator from "@rjsf/validator-ajv8"
import Form from "@rjsf/core"
import FieldTemplate from "@/components/form/custom-templates/field-template"
import FieldErrorTemplate from "@/components/form/custom-templates/field-error-template"
import BaseInputTemplate from "@/components/form/custom-templates/base-input-template"
import ArrayFieldTemplate from "@/components/form/custom-templates/array-field-template"
import {
convertServiceRequestFormToRJSFSchema,
generateUiSchema,
} from "@/lib/utils"
import { RegistryWidgetsType } from "@rjsf/utils"
import CustomCheckboxes from "@/components/form/custom-widgets/custom-checkboxes"
import CustomSelect from "@/components/form/custom-widgets/custom-select"

const widgets: RegistryWidgetsType = {
CheckboxesWidget: CustomCheckboxes,
SelectWidget: CustomSelect,
}

export default function ServiceRequestPage() {
const { serviceRequestId } = useParams()
const serviceRequestIdString = Array.isArray(serviceRequestId)
? serviceRequestId[0]
: serviceRequestId
const router = useRouter()
const { serviceRequest, handleSubmit } = useServiceRequest({
serviceRequestId: serviceRequestIdString,
})

const { name, description, form } = serviceRequest

const uiSchema = generateUiSchema(serviceRequest)
const rjsfSchema = convertServiceRequestFormToRJSFSchema(form)

return (
<>
<div className="flex flex-col justify-start py-10">
<HeaderAccessory />
<div className="flex items-baseline space-x-2">
<Button size="icon" variant="ghost" onClick={() => router.back()}>
<ChevronLeft />
</Button>
<p className="font-bold text-3xl pt-5">{name}</p>
</div>
<p className="text-lg pt-3 ml-12 text-gray-500">{description}</p>
</div>
<div className="w-full flex justify-center">
<div className="w-4/5">
<Form
schema={rjsfSchema}
uiSchema={uiSchema}
validator={validator}
onSubmit={handleSubmit}
templates={{
FieldTemplate,
FieldErrorTemplate,
BaseInputTemplate,
ArrayFieldTemplate,
}}
widgets={widgets}
showErrorList={false}
>
<div className="flex justify-end">
<Button size="lg" type="submit">
Submit
</Button>
</div>
</Form>
</div>
</div>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const createDummyServices = (noOfServices: number) => {
id: i + 1,
name: `Service ${i + 1}`,
description: `Description ${i + 1}`,
form: {},
})
}
return services
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { ServiceRequestWithSteps } from "@/types/service"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { zodResolver } from "@hookform/resolvers/zod"
import { isJson } from "@/lib/utils"
import { KeyboardEvent, KeyboardEventHandler } from "react"
import { validateFormSchema } from "../_utils/validation"

const DEFAULT_FORM = {
input: { title: "", description: "", type: "input", required: true },
select: {
title: "",
description: "",
type: "select",
required: true,
options: ["Option 1", "Option 2", "Option 3"],
},
checkBoxes: {
title: "",
description: "",
type: "checkboxes",
required: false,
options: ["Option 1", "Option 2", "Option 3"],
},
}

const DEFAULT_STEPS = {
Approval: {
name: "Approval",
type: "approval",
next: "",
start: true,
},
}

const createServiceSchema = z.object({
name: z.string().min(1, "Name is required"),
description: z.string(),
form: z
.string()
.min(1, "Form Schema is required")
.superRefine((val, ctx) => {
const errorList = validateFormSchema(val)
if (errorList.length > 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: errorList.join(" , "),
})
}
})
.refine((arg) => isJson(arg), {
message: "Ensure that Form is valid JSON Schema",
}),
steps: z
.string()
.min(1, "Pipeline Steps Schema is required")
.refine((arg) => isJson(arg), {
message: "Ensure that Form is valid JSON Schema",
}),
})
const useCreateService = () => {
const form = useForm<z.infer<typeof createServiceSchema>>({
resolver: zodResolver(createServiceSchema),
defaultValues: {
name: "",
description: "",
form: JSON.stringify(DEFAULT_FORM, null, 4),
steps: JSON.stringify(DEFAULT_STEPS, null, 4),
},
})

const handleSubmitForm = (values: z.infer<typeof createServiceSchema>) => {
const { description, form, name, steps } = values

// TODO: Replace with API call
console.log("Submitting:", {
name,
description,
form: JSON.parse(form),
steps: JSON.parse(steps),
})
}

function handleTextAreaTabKeyDown(event: KeyboardEvent): void {
if (event.key == "Tab") {
event.preventDefault()
const htmlTextElement = event.target as HTMLTextAreaElement
const start = htmlTextElement.selectionStart
const end = htmlTextElement.selectionEnd

htmlTextElement.value =
htmlTextElement.value.substring(0, start) +
"\t" +
htmlTextElement.value.substring(end)

htmlTextElement.selectionStart = htmlTextElement.selectionEnd = start + 1
}
}

return {
form,
handleSubmitForm,
handleTextAreaTabKeyDown,
}
}

export default useCreateService
Loading

0 comments on commit ac627b5

Please sign in to comment.