From b098210aacdc510c6ad149f22ab3eee3732821b4 Mon Sep 17 00:00:00 2001 From: Manolo Battista <81776297+manolo-battista@users.noreply.github.com> Date: Thu, 11 Apr 2024 22:57:12 +0200 Subject: [PATCH] feat: members and improvements (#43) --- .env.development | 2 +- .github/workflows/build-push-docker.yml | 4 + .github/workflows/pr-build-test.yml | 6 +- components/ApiDocs/ApiDocsDrawer.tsx | 2 +- .../AppFormConfigProvider.tsx | 9 + components/InputPassword/InputPassword.tsx | 56 ++++++ components/Layout/Layout.tsx | 5 +- .../Permissions/AddPermissionDialog.tsx | 1 + components/Sidebar/Sidebar.tsx | 25 ++- components/UnitSearch/UnitSearch.tsx | 70 ++++++++ crud/arke/ArkeCrud.tsx | 3 +- crud/arke/AssignParameterCrud.tsx | 81 +++++++-- crud/arke/LinkArkeOrGroup.tsx | 1 + crud/common/CrudAddEdit.tsx | 2 +- crud/common/CrudDelete.tsx | 19 ++- crud/group/columns.tsx | 5 +- crud/member/MemberCrud.tsx | 149 +++++++++++----- crud/parameter/ParameterCrud.tsx | 2 +- crud/user/UserCrud.tsx | 3 +- crud/user/UserDelete.tsx | 2 +- hooks/useArkeTable.ts | 16 +- pages/[project]/groups/index.tsx | 4 +- pages/[project]/members/index.tsx | 160 ++++++++++++------ .../{parameters.tsx => parameters/index.tsx} | 1 - pages/index.tsx | 8 +- pages/login.tsx | 21 +-- 26 files changed, 504 insertions(+), 153 deletions(-) create mode 100644 components/InputPassword/InputPassword.tsx create mode 100644 components/UnitSearch/UnitSearch.tsx rename pages/[project]/{parameters.tsx => parameters/index.tsx} (99%) diff --git a/.env.development b/.env.development index 1e0a6a2..868a666 100644 --- a/.env.development +++ b/.env.development @@ -1,5 +1,5 @@ NEXT_PUBLIC_ARKE_SERVER_URL=http://0.0.0.0:4000 -NEXT_MULTIPROJECT=false +NEXT_MULTIPROJECT=true NEXTAUTH_URL=http://localhost:3100/api/auth NEXTAUTH_SECRET=your_secret NEXT_PUBLIC_ARKE_PROJECT="" diff --git a/.github/workflows/build-push-docker.yml b/.github/workflows/build-push-docker.yml index a53240f..821fb03 100644 --- a/.github/workflows/build-push-docker.yml +++ b/.github/workflows/build-push-docker.yml @@ -5,6 +5,10 @@ on: tags: - "v[0-9]+.[0-9]+.[0-9]+" +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + env: REPO_NAME: arke-console diff --git a/.github/workflows/pr-build-test.yml b/.github/workflows/pr-build-test.yml index 0200f6b..f46afe3 100644 --- a/.github/workflows/pr-build-test.yml +++ b/.github/workflows/pr-build-test.yml @@ -8,6 +8,10 @@ on: branches: - "main" +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + # List of jobs jobs: Check-License: @@ -42,4 +46,4 @@ jobs: files: ./coverage/cobertura-coverage.xml directory: coverage fail_ci_if_error: true - verbose: true \ No newline at end of file + verbose: true diff --git a/components/ApiDocs/ApiDocsDrawer.tsx b/components/ApiDocs/ApiDocsDrawer.tsx index 4b6d541..5dc57a7 100644 --- a/components/ApiDocs/ApiDocsDrawer.tsx +++ b/components/ApiDocs/ApiDocsDrawer.tsx @@ -91,7 +91,7 @@ function ApiDocsDrawer({ diff --git a/components/AppFormConfigProvider/AppFormConfigProvider.tsx b/components/AppFormConfigProvider/AppFormConfigProvider.tsx index eea5313..ba551ea 100644 --- a/components/AppFormConfigProvider/AppFormConfigProvider.tsx +++ b/components/AppFormConfigProvider/AppFormConfigProvider.tsx @@ -19,6 +19,7 @@ import { FormConfigProvider as FCProvider } from "@arkejs/form"; import { Autocomplete, Checkbox, Input, Json } from "@arkejs/ui"; import AutocompleteLink from "@/components/AppFormConfigProvider/components/AutocompleteLink"; import FileDropzone from "@/components/AppFormConfigProvider/components/FileDropzone"; +import InputPassword from "@/components/InputPassword/InputPassword"; export default function AppFormConfigProvider(props: { children: ReactNode }) { return ( @@ -87,6 +88,14 @@ export default function AppFormConfigProvider(props: { children: ReactNode }) { /> ); }, + // @ts-ignore + password: ({ field }) => ( + field.onChange(e.target.value)} + /> + ), boolean: ({ field }) => ( ): void; +} + +export default function InputPassword(props: InputPasswordProps) { + const [showPassword, setShowPassword] = useState(false); + const { value, onChange } = props; + return ( + } + endAdornment={ + setShowPassword(!showPassword)} + > + {!showPassword ? ( + + ) : ( + + )} + + } + /> + ); +} diff --git a/components/Layout/Layout.tsx b/components/Layout/Layout.tsx index fe7e8a9..3957fff 100644 --- a/components/Layout/Layout.tsx +++ b/components/Layout/Layout.tsx @@ -16,11 +16,12 @@ import { PropsWithChildren } from "react"; import Sidebar from "@/components/Sidebar/Sidebar"; -function Layout({ children }: PropsWithChildren<{}>) { +import { User } from "next-auth"; +function Layout({ user, children }: PropsWithChildren<{ user?: User }>) { return ( <>
- +
{children}
diff --git a/components/Permissions/AddPermissionDialog.tsx b/components/Permissions/AddPermissionDialog.tsx index 936b750..b5c1b89 100644 --- a/components/Permissions/AddPermissionDialog.tsx +++ b/components/Permissions/AddPermissionDialog.tsx @@ -91,6 +91,7 @@ export default function AddPermissionDialog({ return ( + + {user && ( +
+ +

+ {user?.first_name} + {user?.last_name} +

+
+ )} +
    diff --git a/components/UnitSearch/UnitSearch.tsx b/components/UnitSearch/UnitSearch.tsx new file mode 100644 index 0000000..875accf --- /dev/null +++ b/components/UnitSearch/UnitSearch.tsx @@ -0,0 +1,70 @@ +/* + * Copyright 2024 Arkemis S.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Autocomplete } from "@arkejs/ui"; +import { TUnit } from "@arkejs/client"; +import React, { useEffect, useState } from "react"; +import useDebounce from "@/hooks/useDebounce"; +import useClient from "@/arke/useClient"; + +export default function UnitSearch({ + arke, + value, + onChange, + placeholder, + label, + config, + renderValue, + renderOption, +}: { + arke: string; + value: TUnit; + onChange: (value: TUnit) => void; + label?: string; + placeholder?: string; + config?(searchedText: string): any; + renderValue?(val: TUnit): any; + renderOption?(val: TUnit): any; +}) { + const client = useClient(); + const [inputValue, setInputValue] = useState(""); + const [values, setValues] = useState([]); + const debouncedInputValue = useDebounce(inputValue, 500); + + useEffect(() => { + if (debouncedInputValue) { + client.unit.getAll(arke, config?.(debouncedInputValue)).then((res) => { + setValues(res.data.content.items); + }); + } + }, [debouncedInputValue]); + + return ( + setInputValue(event.target.value)} + values={values} + value={value} + renderChips={false} + placeholder={placeholder || "Search an unit"} + renderValue={(value) => + renderValue?.(value) ?? (value as TUnit).label ?? (value as TUnit).id + } + renderOption={renderOption} + /> + ); +} diff --git a/crud/arke/ArkeCrud.tsx b/crud/arke/ArkeCrud.tsx index 19aa8ed..573b3f7 100644 --- a/crud/arke/ArkeCrud.tsx +++ b/crud/arke/ArkeCrud.tsx @@ -88,7 +88,7 @@ export function ArkeCrud({ ); return ( - +
    diff --git a/crud/arke/AssignParameterCrud.tsx b/crud/arke/AssignParameterCrud.tsx index 1cad449..f99d917 100644 --- a/crud/arke/AssignParameterCrud.tsx +++ b/crud/arke/AssignParameterCrud.tsx @@ -18,7 +18,13 @@ import { Autocomplete, Button, Chip, Dialog } from "@arkejs/ui"; import React, { useCallback, useEffect, useState } from "react"; import useDebounce from "@/hooks/useDebounce"; import useClient from "@/arke/useClient"; -import { TBaseParameter, TUnit } from "@arkejs/client"; +import { + ConditionalOperator, + Filter, + RelationalOperator, + TBaseParameter, + TUnit, +} from "@arkejs/client"; import { AddIcon, LinkIcon, TrashIcon } from "@/components/Icon"; import toast from "react-hot-toast"; import { ParameterAdd } from "@/crud/parameter"; @@ -60,31 +66,77 @@ function AssignParameterAdd({ }); const [inputValue, setInputValue] = useState(""); const [values, setValues] = useState([]); + const [assigned, setAssigned] = useState([]); const [selected, setSelected] = useState([]); const debouncedInputValue = useDebounce(inputValue, 500); useEffect(() => { setInputValue(""); + loadData(); setValues([]); setSelected([]); + + // Get already assigned + client.arke + .struct(arkeId) + .then((res) => setAssigned(res.data.content.parameters)); }, [open]); useEffect(() => { + loadData(); + }, [selected, debouncedInputValue]); + + function loadData() { + let filters: any = []; + /** + * Create filter for assigned and selected + * @param item + */ + function addFilter(item: TUnit) { + filters.push( + new Filter({ + operator: RelationalOperator.NOT, + value: new Filter({ + operator: RelationalOperator.EQ, + key: "id", + value: item.id, + }), + }) + ); + } if (debouncedInputValue) { - client.group - .getAllUnits("parameter", { - params: { - offset: 0, - limit: 5, - filter: `and(contains(id,${debouncedInputValue}))`, - order: `label;asc`, - }, + filters.push( + new Filter({ + operator: RelationalOperator.ICONTAINS, + key: "id", + value: debouncedInputValue, }) - .then((res) => { - setValues(res.data.content.items); - }); + ); + } + if (assigned.length > 0) { + assigned.forEach((item) => addFilter(item as any)); } - }, [debouncedInputValue]); + if (selected.length > 0) { + selected.forEach((item) => addFilter(item)); + } + filters = new Filter({ + operator: ConditionalOperator.AND, + filters: filters, + }); + + client.group + .getAllUnits("parameter", { + params: { + offset: 0, + limit: 5, + filter: filters.length > 0 ? filters.toString() : undefined, + order: `label;asc`, + }, + }) + .then((res) => { + setValues(res.data.content.items); + }); + } const handleSubmit = useCallback(async () => { await Promise.all( @@ -100,6 +152,7 @@ function AssignParameterAdd({ return ( <> @@ -208,6 +261,7 @@ function AssignParameterDelete({ return ( @@ -282,6 +336,7 @@ function AssignParameterEdit({ return ( diff --git a/crud/arke/LinkArkeOrGroup.tsx b/crud/arke/LinkArkeOrGroup.tsx index db4f671..29d93b7 100644 --- a/crud/arke/LinkArkeOrGroup.tsx +++ b/crud/arke/LinkArkeOrGroup.tsx @@ -91,6 +91,7 @@ function LinkArkeOrGroup({ return ( <> diff --git a/crud/common/CrudAddEdit.tsx b/crud/common/CrudAddEdit.tsx index 9396dae..dcd5b87 100644 --- a/crud/common/CrudAddEdit.tsx +++ b/crud/common/CrudAddEdit.tsx @@ -130,7 +130,7 @@ export function CrudAddEdit(props: CrudProps) { const idField = fields.filter((item) => item.id === "id")?.[0]; const filteredFields = fields.filter((item) => item.id !== "id"); return ( - + {fields.length > 0 ? ( ; onSubmit(data: any): void; + config?: any; } function CrudDelete(props: DeleteProps) { const client = useClient(); - const { arkeId, unitId, open, title, onClose, onBeforeSubmit, onSubmit } = - props; + const { + arkeId, + unitId, + open, + title, + onClose, + onBeforeSubmit, + onSubmit, + config, + } = props; function onDelete() { function onSubmitCallback() { let promise; if (unitId) { - promise = client.unit.delete(arkeId, unitId as string); + promise = client.unit.delete(arkeId, unitId as string, config); } else { - promise = client.arke.delete(arkeId); + promise = client.arke.delete(arkeId, config); } promise.then((res) => { @@ -56,7 +65,7 @@ function CrudDelete(props: DeleteProps) { } return ( - +

    Do you really want to delete?

    diff --git a/crud/parameter/ParameterCrud.tsx b/crud/parameter/ParameterCrud.tsx index 770f66a..2a0158d 100644 --- a/crud/parameter/ParameterCrud.tsx +++ b/crud/parameter/ParameterCrud.tsx @@ -125,7 +125,7 @@ export function ParameterAdd({ }, [onClose]); return ( - + setPassword(e.target.value)} - placeholder="Password" - fullWidth - startAdornment={ - - } - endAdornment={ - setShowPassword(!showPassword)} - > - {!showPassword ? ( - - ) : ( - - )} - - } />