Skip to content

Commit

Permalink
Filter kunde (#556)
Browse files Browse the repository at this point in the history
  • Loading branch information
MariaBonde authored Dec 17, 2024
1 parent e52b597 commit 02d3466
Show file tree
Hide file tree
Showing 12 changed files with 334 additions and 75 deletions.
70 changes: 5 additions & 65 deletions frontend/src/app/[organisation]/kunder/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EngagementPerCustomerReadModel, EngagementState } from "@/api-types";
import CustomerRow from "@/components/CostumerTable/CustomerRow";
import { fetchWithToken } from "@/data/apiCallsWithToken";
import { CustomerFilterProvider } from "@/hooks/CustomerFilterProvider";
import { CustomerContent } from "@/pagecontent/CustomerContent";
import { Metadata } from "next";

export const metadata: Metadata = {
Expand All @@ -26,69 +27,8 @@ export default async function Kunder({
);

return (
<>
<Sidebar />
<div className="main p-4 pt-5 w-full flex flex-col gap-8">
<h1>Kunder</h1>

<CustomerTable
title={"Kunder"}
customers={customers.sort((a, b) =>
a.customerName.localeCompare(b.customerName),
)}
/>
<CustomerTable title={"Permisjoner og ferie"} customers={absence} />
</div>
</>
<CustomerFilterProvider customers={customers}>
<CustomerContent customers={customers} absence={absence} />
</CustomerFilterProvider>
);

function Sidebar() {
return (
<div className="sidebar z-10">
<div className=" bg-primary/5 h-full flex flex-col gap-6 p-4 w-[300px]">
<div className="flex flex-row justify-between items-center">
<h1 className="">Filter</h1>
</div>
Ikke implementert
</div>
</div>
);
}

function CustomerTable({
title,
customers,
}: {
title: string;
customers: EngagementPerCustomerReadModel[];
}) {
return (
<table className="w-full min-w-[700px] table-fixed">
<colgroup>
<col span={1} className="w-16" />
<col span={1} className="w-[300px]" />
{[1].map((_, index) => (
<col key={index} span={1} />
))}
</colgroup>
<thead>
<tr>
<th colSpan={2}>
<div className="flex flex-row gap-3 pb-4 items-center">
<p className="normal-medium ">{title}</p>
<p className="text-primary small-medium rounded-full bg-secondary/30 px-2 py-1">
{customers?.length}
</p>
</div>
</th>
</tr>
</thead>
<tbody>
{customers.map((customer) => (
<CustomerRow key={customer.customerId} customer={customer} />
))}
</tbody>
</table>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export function EditDateInput({
useLayoutEffect(() => {
if (inEdit && inputRef.current && clicked) {
inputRef.current.focus();
inputRef.current.showPicker();
}
}, [inEdit, clicked]);

Expand All @@ -56,7 +57,7 @@ export function EditDateInput({
required={required}
defaultValue={value ? format(value!, "yyyy-MM-dd") : undefined}
type="date"
className="border-one_and_a_half shadow-sm border-primary rounded-md px-2 pt-1 mt-1 block w-full"
className="border-one_and_a_half shadow-sm border-primary rounded-md px-2 pt-1 mt-1 block w-full "
/>
</>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function EditTextarea({
name={name}
defaultValue={value}
aria-label={label}
className="border-one_and_a_half border-primary/80 rounded-md p-2 mt-1 block w-full focus:outline-none focus:bg-primary/10 transitionEase"
className="border-one_and_a_half border-primary/80 rounded-md p-2 mt-1 block w-full focus:outline-none focus:outline-primary"
/>
</>
) : (
Expand Down
26 changes: 26 additions & 0 deletions frontend/src/components/CostumerTable/ActiveCustomerFilters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";
import { useContext } from "react";
import { FilteredCustomerContext } from "@/hooks/CustomerFilterProvider";

export default function ActiveCustomerFilters() {
const filterTextComponents: string[] = [];
const { activeFilters } = useContext(FilteredCustomerContext);

const { searchFilter, engagementIsBillableFilter, bookingTypeFilter } =
activeFilters;

if (searchFilter != "") filterTextComponents.push(` "${searchFilter}"`);
if (engagementIsBillableFilter != "") filterTextComponents.push(`engagement`);
if (bookingTypeFilter != "")
filterTextComponents.push(` "${bookingTypeFilter}"`);

const filterSummaryText = filterTextComponents.join(", ");

return (
<div className="h-4">
{filterSummaryText != "" && (
<p className="small-medium text-primary"> {filterSummaryText} </p>
)}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { FilteredCustomerContext } from "@/hooks/CustomerFilterProvider";
import SearchBarComponent from "../SearchBarComponent";

export default function CustomerSidebarWithFilters() {
return (
<>
<div className="sidebar z-10 bg-background_grey h-full flex flex-col gap-6 p-4 w-[300px] overflow-y-auto">
<div className="flex flex-row justify-between items-center">
<h1 className="">Filter</h1>
</div>
<SearchBarComponent context={FilteredCustomerContext} />
</div>
</>
);
}
39 changes: 39 additions & 0 deletions frontend/src/components/CostumerTable/FilteredCustomersTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";
import { EngagementPerCustomerReadModel } from "@/api-types";
import CustomerRow from "@/components/CostumerTable/CustomerRow";
import { useCustomerFilter } from "@/hooks/staffing/useCustomerFilter";

function FilteredCustomerTable() {
const filteredCustomers = useCustomerFilter();

return (
<table className="w-full min-w-[700px] table-fixed">
<colgroup>
<col span={1} className="w-16" />
<col span={1} className="w-[300px]" />
{[1].map((_, index) => (
<col key={index} span={1} />
))}
</colgroup>
<thead>
<tr>
<th colSpan={2}>
<div className="flex flex-row gap-3 pb-4 items-center">
<p className="normal-medium ">Kunder</p>
<p className="text-primary small-medium rounded-full bg-secondary/30 px-2 py-1">
{filteredCustomers?.length}
</p>
</div>
</th>
</tr>
</thead>
<tbody>
{filteredCustomers.map((customer: EngagementPerCustomerReadModel) => (
<CustomerRow key={customer.customerId} customer={customer} />
))}
</tbody>
</table>
);
}

export { FilteredCustomerTable };
6 changes: 4 additions & 2 deletions frontend/src/components/SearchBarComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
"use client";
import { useContext, useEffect, useRef, useState } from "react";
import { Context, useContext, useEffect, useRef, useState } from "react";
import { Search } from "react-feather";
import { useNameSearch } from "@/hooks/staffing/useNameSearch";
import { FilteredContext } from "@/hooks/ConsultantFilterProvider";

export default function SearchBarComponent({
hidden = false,
context,
}: {
hidden?: boolean;
context: Context<any>;
}) {
const { setNameSearch, activeNameSearch } = useNameSearch();
const { setNameSearch, activeNameSearch } = useNameSearch(context);
const inputRef = useRef<HTMLInputElement>(null);
const [searchIsActive, setIsSearchActive] = useState(false);
const { isDisabledHotkeys } = useContext(FilteredContext);
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/components/StaffingSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ArrowLeft } from "react-feather";
import RawYearsFilter from "./RawYearsFilter";
import CompetenceFilter from "./CompetenceFilter";
import ExperienceFilter from "./ExperienceFilter";
import { FilteredContext } from "@/hooks/ConsultantFilterProvider";

// @ts-ignore
export default function StaffingSidebar({
Expand All @@ -30,7 +31,7 @@ export default function StaffingSidebar({
<ArrowLeft className="text-primary" size="24" />
</button>
</div>
<SearchBarComponent />
<SearchBarComponent context={FilteredContext} />
{isStaffing ? <AvailabilityFilter /> : null}
<DepartmentFilter />
<RawYearsFilter />
Expand All @@ -40,7 +41,10 @@ export default function StaffingSidebar({
)}
{!isSidebarOpen && (
<div className="sidebar z-10">
<SearchBarComponent hidden={!isSidebarOpen} />
<SearchBarComponent
context={FilteredContext}
hidden={!isSidebarOpen}
/>
</div>
)}
</>
Expand Down
106 changes: 106 additions & 0 deletions frontend/src/hooks/CustomerFilterProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"use client";

import { EngagementPerCustomerReadModel, EngagementState } from "@/api-types";
import { usePathname, useSearchParams } from "next/navigation";
import { createContext, ReactNode, useEffect, useState } from "react";

export type CustomerFilters = {
searchFilter: string;
engagementIsBillableFilter: boolean | string;
bookingTypeFilter: EngagementState | string;
};

interface UpdateFilterParams {
search?: string;
engagementIsBillable?: boolean;
bookingType?: EngagementState;
}
export type UpdateFilters = (updateParams: UpdateFilterParams) => void;
const defaultFilters: CustomerFilters = {
searchFilter: "",
engagementIsBillableFilter: "",
bookingTypeFilter: "",
};

type CustomerFilterContextType = {
activeFilters: CustomerFilters;
updateFilters: UpdateFilters;
customers: EngagementPerCustomerReadModel[];
setCustomers: React.Dispatch<
React.SetStateAction<EngagementPerCustomerReadModel[]>
>;
};

export const FilteredCustomerContext = createContext<CustomerFilterContextType>(
{
customers: [],
setCustomers: () => null,
activeFilters: defaultFilters,
updateFilters: () => null,
},
);

export function CustomerFilterProvider(props: {
customers: EngagementPerCustomerReadModel[];
children: ReactNode;
}) {
const [customers, setCustomers] = useState<EngagementPerCustomerReadModel[]>(
[],
);
const [activeFilters, updateFilters] = useUrlRouteFilter();

useEffect(() => setCustomers(props.customers), [props.customers]);

return (
<FilteredCustomerContext.Provider
value={{
...props,
customers,
setCustomers,
activeFilters,
updateFilters,
}}
>
{props.children}
</FilteredCustomerContext.Provider>
);
}

function useUrlRouteFilter(): [CustomerFilters, UpdateFilters] {
const pathname = usePathname();
const searchParams = useSearchParams();

const [searchFilter, setSearchFilter] = useState(
searchParams.get("search") || "",
);
const [engagementIsBillableFilter, setEngagementIsBillableFilter] = useState(
searchParams.get("engagementIsBillable") || false,
);
const [bookingTypeFilter, setBookingTypeFilter] = useState(
searchParams.get("bookingType") || "",
);

function updateRoute(updateParams: UpdateFilterParams) {
// If not defined, defaults to current value:
const { search = searchFilter } = updateParams;
const { engagementIsBillable = engagementIsBillableFilter } = updateParams;
const { bookingType = bookingTypeFilter } = updateParams;

const url = `${pathname}?search=${search}&engagementIsBillable=${engagementIsBillable}&bookingType=${bookingType}}`;

setSearchFilter(search);
setEngagementIsBillableFilter(engagementIsBillable);
setBookingTypeFilter(bookingType);

window.history.pushState({}, "", url);
}

return [
{
searchFilter,
engagementIsBillableFilter,
bookingTypeFilter,
},
updateRoute,
];
}
Loading

0 comments on commit 02d3466

Please sign in to comment.