Skip to content

Commit

Permalink
Data for detailed booking and booking transformed for forecast rows (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
MariaBonde authored Jan 10, 2025
1 parent 534a1b0 commit 3da9690
Show file tree
Hide file tree
Showing 10 changed files with 901 additions and 133 deletions.
439 changes: 414 additions & 25 deletions frontend/mockdata/mockData.ts

Large diffs are not rendered by default.

12 changes: 2 additions & 10 deletions frontend/src/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
* ---------------------------------------------------------------
*/

import { Forecast } from "./types";

export interface BookedHoursPerWeek {
/** @format int32 */
year: number;
Expand All @@ -20,7 +22,6 @@ export interface BookedHoursPerWeek {
dateString: string;
bookingModel: WeeklyBookingReadModel;
}

export interface BookingDetails {
/** @minLength 1 */
projectName: string;
Expand Down Expand Up @@ -66,15 +67,6 @@ export interface ConsultantReadModel {
forecasts?: Forecast[];
}

export interface Forecast {
id: number;
month: number;
year: number;
forecastValue: number;
hasBeenChanged: boolean;
valueAddedManually: number;
}

export interface ConsultantWriteModel {
/** @minLength 1 */
name: string;
Expand Down
14 changes: 11 additions & 3 deletions frontend/src/components/Forecast/ForecastRows.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
"use client";
import { ConsultantReadModel, ProjectWithCustomerModel } from "@/api-types";
import React, { useContext, useEffect, useState } from "react";
import { AlertCircle, CheckCircle, ChevronDown, Plus } from "react-feather";
import { Plus } from "react-feather";
import { DetailedBookingRows } from "@/components/Staffing/DetailedBookingRows";
import { WeekCell } from "@/components/Staffing/WeekCell";
import { useModal } from "@/hooks/useModal";
import { usePathname } from "next/navigation";
import {
Expand All @@ -15,8 +14,11 @@ import { setDetailedBookingHours } from "@/components/Staffing/DetailedBookingRo
import { FilteredContext } from "@/hooks/ConsultantFilterProvider";
import { DateTime } from "luxon";
import Image from "next/image";
import { INTERNAL_CUSTOMER_NAME } from "../Staffing/helpers/utils";
import { MonthCell } from "./MonthCell";
import {
bookingForMonth,
transformToMonthlyData,
} from "./TransformWeekDataToMonth";

export default function ForecastRows({
consultant,
Expand Down Expand Up @@ -68,6 +70,7 @@ export default function ForecastRows({
} = useModal({
closeOnBackdropClick: false,
});
const bookingsPerMonth = transformToMonthlyData(consultant.bookings);

const [selectedProjectId, setSelectedProjectId] = useState<
number | undefined
Expand Down Expand Up @@ -211,6 +214,11 @@ export default function ForecastRows({
</td>
{currentConsultant.forecasts?.map((b, index) => (
<MonthCell
bookedHoursPerMonth={bookingForMonth(
bookingsPerMonth,
b.month,
b.year,
)}
key={index}
hasBeenEdited={b.hasBeenChanged}
forecastValue={b.forecastValue + b.valueAddedManually}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/Forecast/ForecastTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Calendar } from "react-feather";
import React, { useContext } from "react";
import { FilteredContext } from "@/hooks/ConsultantFilterProvider";
import ForecastRows from "./ForecastRows";
import { MockConsultants } from "../../../mockdata/mockData";
import { MockConsultantsForForecast } from "../../../mockdata/mockData";

const months = [
"Januar",
Expand Down Expand Up @@ -127,7 +127,7 @@ export default function ForecastTable() {
</tr>
</thead>
<tbody>
{MockConsultants.map((consultant) => (
{MockConsultantsForForecast.map((consultant) => (
<ForecastRows
key={consultant.id}
consultant={consultant}
Expand Down
129 changes: 129 additions & 0 deletions frontend/src/components/Forecast/HoveredMonth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { BookingType, ConsultantReadModel } from "@/api-types";
import {
getColorByStaffingType,
getIconByBookingType,
} from "@/components/Staffing/helpers/utils";
import React from "react";
import {
transformDetailedBookingToMonthlyData,
transformToMonthlyData,
} from "./TransformWeekDataToMonth";
import { MonthlyDetailedBooking, MonthlyHours } from "@/types";

function isMonthBookingZeroHours(
detailedBooking: MonthlyDetailedBooking,
hoveredRowMonth: number,
): boolean {
return (
detailedBooking.hours.filter(
(monthHours) =>
monthHours.month % 100 == hoveredRowMonth && monthHours.hours != 0,
).length == 0
);
}
export function HoveredMonth(props: {
hoveredRowMonth: number;
consultant: ConsultantReadModel;
isLastCol: boolean;
isSecondLastCol: boolean;
columnCount: number;
}) {
const {
hoveredRowMonth: hoveredRowMonth,
consultant,
isLastCol,
isSecondLastCol,
columnCount,
} = props;

const bookings = transformToMonthlyData(consultant.bookings);
const detailedBookings = transformDetailedBookingToMonthlyData(
consultant.detailedBooking,
);
const nonZeroHoursDetailedBookings = detailedBookings.filter(
(d) => !isMonthBookingZeroHours(d, hoveredRowMonth),
);

const freeTime = bookings.find((b) => b.month == hoveredRowMonth)
?.bookingModel.totalSellableTime;
if (freeTime && freeTime > 0) {
nonZeroHoursDetailedBookings.push({
bookingDetails: {
type: BookingType.Available,
projectName: "",
customerName: "Ledig Tid",
projectId: 0,
isBillable: false,
},
hours: [
{
month: hoveredRowMonth,
hours:
bookings.find((b) => b.month == hoveredRowMonth)?.bookingModel
.totalSellableTime || 0,
},
],
});
}

return (
<>
<div
className={`rounded-lg bg-white gap-3 min-w-[222px] p-3 shadow-xl absolute bottom-full mb-2 flex flex-col z-20 pointer-events-none ${
isLastCol || (isSecondLastCol && columnCount >= 26)
? "right-0 "
: "left-1/2 -translate-x-1/2"
} ${nonZeroHoursDetailedBookings.length == 0 && "hidden"}`}
>
{nonZeroHoursDetailedBookings.map((detailedBooking, index) => (
<div
key={index}
className={`flex flex-row gap-2 justify-between items-center
${
index < nonZeroHoursDetailedBookings.length - 1 &&
"pb-3 border-b border-black/10"
}`}
>
<div className="flex flex-row gap-2 items-center">
<div
className={`h-8 w-8 flex justify-center align-middle items-center rounded ${getColorByStaffingType(
detailedBooking.bookingDetails.type,
)}`}
>
{getIconByBookingType(detailedBooking.bookingDetails.type, 16)}
</div>
<div className="flex flex-col">
<p
className={`xsmall text-black/75 ${
!(
detailedBooking.bookingDetails.type ==
BookingType.Offer ||
detailedBooking.bookingDetails.type == BookingType.Booking
) && "hidden"
}`}
>
{detailedBooking.bookingDetails.projectName}
</p>
<p className="small text-black whitespace-nowrap">
{detailedBooking.bookingDetails.customerName}
</p>
</div>
</div>
<p className="small text-black/75">
{
detailedBooking.hours.find(
(hour: MonthlyHours) => hour.month % 100 == hoveredRowMonth,
)?.hours
}
</p>
</div>
))}
</div>
<div
className={`absolute bottom-full mb-[2px] left-1/2 -translate-x-1/2 flex items-center z-50 w-0 h-0 border-l-[8px] border-l-transparent border-t-[8px] border-t-white border-r-[8px] border-r-transparent pointer-events-none ${
nonZeroHoursDetailedBookings.length == 0 && "hidden"
}`}
></div>
</>
);
}
Loading

0 comments on commit 3da9690

Please sign in to comment.