From fa1a768903ebf638446d9c0322edc562dae8fc07 Mon Sep 17 00:00:00 2001 From: Sigrid Elnan Date: Mon, 9 Oct 2023 14:41:16 +0200 Subject: [PATCH 1/8] Add api call for departments --- backend/Api/Departments/DeparmentController.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 backend/Api/Departments/DeparmentController.cs diff --git a/backend/Api/Departments/DeparmentController.cs b/backend/Api/Departments/DeparmentController.cs new file mode 100644 index 00000000..91c99176 --- /dev/null +++ b/backend/Api/Departments/DeparmentController.cs @@ -0,0 +1,16 @@ +using Database.DatabaseContext; +using Microsoft.AspNetCore.Mvc; + +[Route("/departments")] +[ApiController] +public class DepartmentController : ControllerBase { + + [HttpGet] + public ActionResult> Get(ApplicationContext applicationContext){ + + return applicationContext.Department.Select(d => new DepartmentReadModel(d.Id, d.Name)).ToList(); + + } +} + +public record DepartmentReadModel(string Id, string Name); From 4b66e11ae18703578b5e08c91785a8f2e84d1fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathilde=20Hauk=C3=B8=20Haugum?= Date: Wed, 11 Oct 2023 14:49:45 +0200 Subject: [PATCH 2/8] Add new font --- frontend/src/app/globals.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index d844075f..b2a37e94 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -104,4 +104,10 @@ flex: 1; background-color: transparent; } + + .interaction-chip { + font-size: 0.75rem; + font-family: "Graphik-SemiBold"; + line-height: 0.875rem; + } } From 3483a8108530ee211bf057b96929f6e606004a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathilde=20Hauk=C3=B8=20Haugum?= Date: Wed, 11 Oct 2023 14:50:54 +0200 Subject: [PATCH 3/8] Avoid search overwriting filter --- frontend/src/components/SearchBarComponent.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/SearchBarComponent.tsx b/frontend/src/components/SearchBarComponent.tsx index ecfa1047..0e146b4d 100644 --- a/frontend/src/components/SearchBarComponent.tsx +++ b/frontend/src/components/SearchBarComponent.tsx @@ -1,16 +1,18 @@ "use client"; -import { useRouter } from "next/navigation"; +import { useRouter, useSearchParams } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { Search } from "react-feather"; export default function SearchBarComponent() { const router = useRouter(); + const searchParams = useSearchParams(); const [searchText, setSearchText] = useState(""); const inputRef = useRef(null); useEffect(() => { - router.push(`/bemanning?search=${searchText}`); - }, [searchText, router]); + const currentFilter = searchParams.get("filter") || ""; + router.push(`/bemanning?search=${searchText}&filter=${currentFilter}`); + }, [searchText, searchParams, router]); useEffect(() => { function keyDownHandler(e: { code: string }) { From 0f8acd8f02a5eb76a25632f513aa8eab341ec570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathilde=20Hauk=C3=B8=20Haugum?= Date: Wed, 11 Oct 2023 14:51:03 +0200 Subject: [PATCH 4/8] Add transparent color to tailwind --- frontend/tailwind.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 13c3e9e1..31563393 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -11,6 +11,7 @@ module.exports = { primary_l4: "#F6F5F9", secondary_default: "#F076A6", neutral_l1: "#858585", + transparent: "transparent", }, extend: {}, screens: { From 1868a103fa6b3ca44a786acc496336750e7e3aac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathilde=20Hauk=C3=B8=20Haugum?= Date: Wed, 11 Oct 2023 14:54:31 +0200 Subject: [PATCH 5/8] Add department filter to list of consultants --- frontend/src/app/bemanning/layout.tsx | 2 + frontend/src/components/DepartmentFilter.tsx | 18 +++++++++ frontend/src/components/FilterButton.tsx | 39 +++++++++++++++++++ .../components/FilteredConsultantsList.tsx | 15 ++++--- 4 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 frontend/src/components/DepartmentFilter.tsx create mode 100644 frontend/src/components/FilterButton.tsx diff --git a/frontend/src/app/bemanning/layout.tsx b/frontend/src/app/bemanning/layout.tsx index c4f698ff..e84dc83b 100644 --- a/frontend/src/app/bemanning/layout.tsx +++ b/frontend/src/app/bemanning/layout.tsx @@ -1,3 +1,4 @@ +import DepartmentFilter from "@/components/DepartmentFilter"; import SearchBarComponent from "@/components/SearchBarComponent"; export default function BemanningLayout({ @@ -10,6 +11,7 @@ export default function BemanningLayout({

Filter

+
{children}
diff --git a/frontend/src/components/DepartmentFilter.tsx b/frontend/src/components/DepartmentFilter.tsx new file mode 100644 index 00000000..eacf6861 --- /dev/null +++ b/frontend/src/components/DepartmentFilter.tsx @@ -0,0 +1,18 @@ +import FilterButton from "./FilterButton"; + +export default async function DepartmentFilter() { + const locations = ["Trondheim", "Bergen", "Oslo", "Stockholm"]; //TODO: Update with data fra DB + + return ( +
+
+

Avdelinger

+
+ {locations?.map((location, index) => ( + + ))} +
+
+
+ ); +} diff --git a/frontend/src/components/FilterButton.tsx b/frontend/src/components/FilterButton.tsx new file mode 100644 index 00000000..0926b566 --- /dev/null +++ b/frontend/src/components/FilterButton.tsx @@ -0,0 +1,39 @@ +"use client"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useState } from "react"; + +export default function FilterButton({ filterName }: { filterName: string }) { + const router = useRouter(); + const searchParams = useSearchParams(); + const [isButtonActive, setIsButtonActive] = useState(false); + + function handleFilterClick() { + setIsButtonActive((prevState) => !prevState); + + const currentSearch = searchParams.get("search"); + const currentFilter = searchParams.get("filter") || ""; + const filters = currentFilter.split(","); + const filterIndex = filters.indexOf(filterName); + const newFilters = [...filters]; + if (filterIndex === -1) { + newFilters.push(filterName); + } else { + newFilters.splice(filterIndex, 1); + } + const newFilterString = newFilters.join(","); + router.push(`/bemanning?search=${currentSearch}&filter=${newFilterString}`); + } + + return ( + + ); +} diff --git a/frontend/src/components/FilteredConsultantsList.tsx b/frontend/src/components/FilteredConsultantsList.tsx index 7c04bcc9..96750e34 100644 --- a/frontend/src/components/FilteredConsultantsList.tsx +++ b/frontend/src/components/FilteredConsultantsList.tsx @@ -11,18 +11,23 @@ export default function FilteredConsultantList({ }) { const searchParams = useSearchParams(); const search = searchParams.get("search"); + const filter = searchParams.get("filter"); const [filteredConsultants, setFilteredConsultants] = useState(consultants); useEffect(() => { + var newFilteredConsultants = consultants; if (search && search.length > 0) { - const filtered = consultants?.filter((consultant) => + newFilteredConsultants = newFilteredConsultants?.filter((consultant) => consultant.name.toLowerCase().includes(search.toLowerCase()), ); - setFilteredConsultants(filtered); - } else { - setFilteredConsultants(consultants); } - }, [search, consultants]); + if (filter && filter.length > 0) { + newFilteredConsultants = newFilteredConsultants?.filter((consultant) => + filter.toLowerCase().includes(consultant.department.toLowerCase()), + ); + } + setFilteredConsultants(newFilteredConsultants); + }, [consultants, filter, search]); return (
From ee39aab281ed8853ed46737db13b820f6989b238 Mon Sep 17 00:00:00 2001 From: Sigrid Elnan Date: Wed, 11 Oct 2023 15:50:13 +0200 Subject: [PATCH 6/8] Use ReactQuery to fetch departments --- frontend/src/components/DepartmentFilter.tsx | 10 +++--- frontend/src/hooks/useDepartmentsApi.ts | 38 ++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 frontend/src/hooks/useDepartmentsApi.ts diff --git a/frontend/src/components/DepartmentFilter.tsx b/frontend/src/components/DepartmentFilter.tsx index eacf6861..3ce864fb 100644 --- a/frontend/src/components/DepartmentFilter.tsx +++ b/frontend/src/components/DepartmentFilter.tsx @@ -1,15 +1,17 @@ +"use client"; import FilterButton from "./FilterButton"; +import useDepartmentsApi from "@/hooks/useDepartmentsApi"; -export default async function DepartmentFilter() { - const locations = ["Trondheim", "Bergen", "Oslo", "Stockholm"]; //TODO: Update with data fra DB +export default function DepartmentFilter() { + const { data } = useDepartmentsApi(); return (

Avdelinger

- {locations?.map((location, index) => ( - + {data?.map((location, index) => ( + ))}
diff --git a/frontend/src/hooks/useDepartmentsApi.ts b/frontend/src/hooks/useDepartmentsApi.ts new file mode 100644 index 00000000..60ea4b2b --- /dev/null +++ b/frontend/src/hooks/useDepartmentsApi.ts @@ -0,0 +1,38 @@ +"use client"; +import { fetchWithToken } from "@/utils/ApiUtils"; +import { useIsAuthenticated } from "@azure/msal-react"; +import { useQuery, useQueryClient } from "react-query"; +import { useEffect } from "react"; + +interface Department { + id: string; + name: string; +} + +function useDepartmentsApi() { + const isAuthenticated = useIsAuthenticated(); + const client = useQueryClient(); + + useEffect(() => client.clear(), [client]); //TODO: We need a better way of handling state/cache. This works for now though, but it's a bit hacky ngl + + return useQuery({ + queryKey: "departments", + queryFn: async () => { + if (isAuthenticated) { + try { + const response: Department[] = + await fetchWithToken(`/api/departments`); + return response; + } catch (err) { + console.error(err); + return []; + } + } + // If not authenticated, return an empty array + return []; + }, + refetchOnWindowFocus: false, + }); +} + +export default useDepartmentsApi; From 214142bb8ee226ef1aa088f40e3b6c1b58695626 Mon Sep 17 00:00:00 2001 From: Sigrid Elnan Date: Thu, 12 Oct 2023 09:09:46 +0200 Subject: [PATCH 7/8] Use db data, remove comma at beginning of string --- frontend/src/components/AppProviders.tsx | 61 ++++++++++---------- frontend/src/components/DepartmentFilter.tsx | 29 ++++++---- frontend/src/components/FilterButton.tsx | 9 ++- frontend/src/hooks/useDepartmentsApi.ts | 12 +--- frontend/src/hooks/useVibesApi.ts | 48 +++++++-------- frontend/src/types.ts | 13 ++++- 6 files changed, 94 insertions(+), 78 deletions(-) diff --git a/frontend/src/components/AppProviders.tsx b/frontend/src/components/AppProviders.tsx index cfee9b12..410e3855 100644 --- a/frontend/src/components/AppProviders.tsx +++ b/frontend/src/components/AppProviders.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import { EventType } from "@azure/msal-browser"; import { MsalProvider } from "@azure/msal-react"; import { CssBaseline } from "@mui/material"; @@ -7,38 +7,39 @@ import { msalInstance } from "../utils/msalInstance"; import PageLayout from "./PageLayout"; import ThemeRegistry from "./ThemeRegistry/ThemeRegistry"; - msalInstance.initialize().then(() => { - // Account selection logic is app dependent. Adjust as needed for different use cases. - const accounts = msalInstance.getAllAccounts(); - if (accounts.length > 0) { - msalInstance.setActiveAccount(accounts[0]); - } + // Account selection logic is app dependent. Adjust as needed for different use cases. + const accounts = msalInstance.getAllAccounts(); + if (accounts.length > 0) { + msalInstance.setActiveAccount(accounts[0]); + } - msalInstance.addEventCallback((event) => { - // Types are outdated: Event payload is an object with account, token, etcw - //@ts-ignore - if (event.eventType === EventType.LOGIN_SUCCESS && event.payload.account) { - //@ts-ignore - const account = event.payload.account; - msalInstance.setActiveAccount(account); - } - }); + msalInstance.addEventCallback((event) => { + // Types are outdated: Event payload is an object with account, token, etcw + //@ts-ignore + if (event.eventType === EventType.LOGIN_SUCCESS && event.payload.account) { + //@ts-ignore + const account = event.payload.account; + msalInstance.setActiveAccount(account); + } + }); }); -const queryClient = new QueryClient() - -export default function AppProviders({ children }: { children: React.ReactNode }) { - - return ( - - - - - {children} - - - - ) +const queryClient = new QueryClient(); +export default function AppProviders({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + + + {children} + + + + ); } diff --git a/frontend/src/components/DepartmentFilter.tsx b/frontend/src/components/DepartmentFilter.tsx index 3ce864fb..de46c300 100644 --- a/frontend/src/components/DepartmentFilter.tsx +++ b/frontend/src/components/DepartmentFilter.tsx @@ -1,20 +1,27 @@ "use client"; import FilterButton from "./FilterButton"; import useDepartmentsApi from "@/hooks/useDepartmentsApi"; +import { CircularProgress } from "@mui/material"; export default function DepartmentFilter() { - const { data } = useDepartmentsApi(); + const { data, isLoading } = useDepartmentsApi(); - return ( -
-
-

Avdelinger

-
- {data?.map((location, index) => ( - - ))} + if (isLoading) { + ; + } + + if (data) { + return ( +
+
+

Avdelinger

+
+ {data?.map((department, index) => ( + + ))} +
-
- ); + ); + } } diff --git a/frontend/src/components/FilterButton.tsx b/frontend/src/components/FilterButton.tsx index 0926b566..054ba132 100644 --- a/frontend/src/components/FilterButton.tsx +++ b/frontend/src/components/FilterButton.tsx @@ -5,7 +5,7 @@ import { useState } from "react"; export default function FilterButton({ filterName }: { filterName: string }) { const router = useRouter(); const searchParams = useSearchParams(); - const [isButtonActive, setIsButtonActive] = useState(false); + const [isButtonActive, setIsButtonActive] = useState(checkFilterInUrl); function handleFilterClick() { setIsButtonActive((prevState) => !prevState); @@ -20,10 +20,15 @@ export default function FilterButton({ filterName }: { filterName: string }) { } else { newFilters.splice(filterIndex, 1); } - const newFilterString = newFilters.join(","); + const newFilterString = newFilters.join(",").replace(/^,/, ""); router.push(`/bemanning?search=${currentSearch}&filter=${newFilterString}`); } + function checkFilterInUrl() { + const currentFilter = searchParams.get("filter") || ""; + return currentFilter.includes(filterName); + } + return (