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

Filter kunde #556

Merged
merged 8 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading