Skip to content

Commit

Permalink
Add absence via modal (#384)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasbjoralt authored Dec 12, 2023
1 parent a0c7314 commit 6a1d23b
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 57 deletions.
21 changes: 20 additions & 1 deletion backend/Api/Projects/ProjectController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ namespace Api.Projects;
[ApiController]
public class ProjectController : ControllerBase
{
private const string AbsenceCustomerName = "Permisjoner";

private readonly IMemoryCache _cache;
private readonly ApplicationContext _context;

Expand All @@ -30,7 +32,11 @@ public ActionResult<List<EngagementPerCustomerReadModel>> Get(
var selectedOrgId = _context.Organization.SingleOrDefault(org => org.UrlKey == orgUrlKey);
if (selectedOrgId is null) return BadRequest();

return _context.Project.Include(project => project.Customer)
var absenceReadModels = new EngagementPerCustomerReadModel(-1, AbsenceCustomerName,
_context.Absence.Select(absence =>
new EngagementReadModel(absence.Id, absence.Name, EngagementState.Absence, false)).ToList());

var projectReadModels = _context.Project.Include(project => project.Customer)
.Where(project => project.Customer.Organization.UrlKey == orgUrlKey)
.GroupBy(project => project.Customer)
.Select(a =>
Expand All @@ -40,6 +46,9 @@ public ActionResult<List<EngagementPerCustomerReadModel>> Get(
a.Select(e =>
new EngagementReadModel(e.Id, e.Name, e.State, false)).ToList()))
.ToList();

projectReadModels.Add(absenceReadModels);
return projectReadModels;
}

[HttpDelete]
Expand Down Expand Up @@ -153,6 +162,9 @@ public ActionResult<ProjectWithCustomerModel> Put([FromRoute] string orgUrlKey,
var selectedOrg = _context.Organization.SingleOrDefault(org => org.UrlKey == orgUrlKey);
if (selectedOrg is null) return BadRequest("Selected org not found");

if (body.CustomerName == AbsenceCustomerName)
return Ok(HandleAbsenceChange(body));

var customer = service.UpdateOrCreateCustomer(selectedOrg, body.CustomerName, orgUrlKey);

var project = _context.Project
Expand Down Expand Up @@ -186,4 +198,11 @@ public ActionResult<ProjectWithCustomerModel> Put([FromRoute] string orgUrlKey,

return Ok(responseModel);
}

private ProjectWithCustomerModel HandleAbsenceChange(EngagementWriteModel body)
{
var absence = _context.Absence.Single(a => a.Name == body.ProjectName);
return new ProjectWithCustomerModel(absence.Name, AbsenceCustomerName, EngagementState.Absence, false,
absence.Id);
}
}
1 change: 1 addition & 0 deletions backend/Core/DomainModels/Engagement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public enum EngagementState
Order,
Lost,
Offer,
Absence,

[Obsolete("'Active' is no longer used. Please use 'Order' instead")]
Active
Expand Down
1 change: 1 addition & 0 deletions frontend/src/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export enum EngagementState {
Order = "Order",
Lost = "Lost",
Offer = "Offer",
Absence = "Absence",
Active = "Active",
}

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Modals/LargeModalHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function LargeModalHeader({
return (
<div className="flex flex-row gap-3 items-center">
<div
className={`w-[60px] h-[60px] bg-offer rounded-lg flex justify-center items-center ${projectStateColor}`}
className={`w-[60px] h-[60px] rounded-lg flex justify-center items-center ${projectStateColor}`}
>
{getIconByProjectState(32, project?.bookingType)}
</div>
Expand Down
106 changes: 57 additions & 49 deletions frontend/src/components/Staffing/AddEngagementForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { MultiValue } from "react-select";
import EasyModal from "@/components/Modals/EasyModal";
import {
ConsultantReadModel,
EngagementWriteModel,
EngagementState,
EngagementWriteModel,
ProjectWithCustomerModel,
} from "@/api-types";
import { usePathname } from "next/navigation";
Expand All @@ -30,7 +30,6 @@ export function AddEngagementForm({
easyModalRef: RefObject<HTMLDialogElement>;
consultant: ConsultantReadModel;
}) {
const { closeModalOnBackdropClick } = useContext(FilteredContext);
const { customers, consultants, setIsDisabledHotkeys } =
useContext(FilteredContext);

Expand All @@ -50,9 +49,7 @@ export function AddEngagementForm({
const [selectedConsultants, setSelectedConsultants] =
useState<MultiValue<SelectOption> | null>([selectedConsultant]);

const [project, setProject] = useState<
ProjectWithCustomerModel | undefined
>();
const [_, setProject] = useState<ProjectWithCustomerModel | undefined>();

const customerOptions = customers.map(
(c) =>
Expand Down Expand Up @@ -88,6 +85,9 @@ export function AddEngagementForm({
// State for toggle
const [isFakturerbar, setIsFakturerbar] = useState(true);

// Hardcoded, based on ID from backend. Hopefully we can find a more graceful solution in the future
const isAbsence = selectedCustomer?.value == -1;

// Handler for select components
function handleSelectedCustomerChange(newCustomer: SelectOption) {
setSelectedCustomer(newCustomer);
Expand Down Expand Up @@ -134,7 +134,7 @@ export function AddEngagementForm({
const body: EngagementWriteModel = {
customerName: selectedCustomer?.label,
projectName: selectedEngagement?.label,
bookingType: radioValue,
bookingType: isAbsence ? EngagementState.Absence : radioValue,
isBillable: isFakturerbar,
};

Expand Down Expand Up @@ -215,51 +215,59 @@ export function AddEngagementForm({
isDisabled={selectedCustomer == null}
/>
</div>
{/* Radio Button Group */}
<div
className={`flex flex-row gap-4 ${
selectedEngagement == null && "hidden"
}`}
>
<label className="flex gap-2 normal items-center">
<input
type="radio"
value={EngagementState.Offer}
checked={radioValue === EngagementState.Offer}
onChange={handleRadioChange}
/>
Tilbud
</label>
<label className="flex gap-2 normal items-center">
<input
type="radio"
value={EngagementState.Order}
checked={radioValue === EngagementState.Order}
onChange={handleRadioChange}
/>
Ordre
</label>
</div>
{/* Toggle (Checkbox) */}
<label
className={`flex flex-row justify-between items-center normal ${
selectedEngagement == null && "hidden"
}`}
>
Fakturerbart
<div
className={`rounded-full w-[52px] h-7 flex items-center ${
isFakturerbar ? "bg-primary" : "bg-black/20"
}`}
onClick={handleToggleChange}
>

{!isAbsence && (
<>
{/* Radio Button Group */}

<div
className={`m-[2px] bg-white rounded-full w-6 h-6 ${
isFakturerbar && " translate-x-6"
className={`flex flex-row gap-4 ${
selectedEngagement == null && "hidden"
}`}
>
<label className="flex gap-2 normal items-center">
<input
type="radio"
value={EngagementState.Offer}
checked={radioValue === EngagementState.Offer}
onChange={handleRadioChange}
disabled={selectedCustomer?.value == -1}
/>
Tilbud
</label>
<label className="flex gap-2 normal items-center">
<input
type="radio"
value={EngagementState.Order}
checked={radioValue === EngagementState.Order}
onChange={handleRadioChange}
disabled={isAbsence}
/>
Ordre
</label>
</div>
{/* Toggle (Checkbox) */}
<label
className={`flex flex-row justify-between items-center normal ${
selectedEngagement == null && "hidden"
}`}
></div>
</div>
</label>
>
Fakturerbart
<div
className={`rounded-full w-[52px] h-7 flex items-center ${
isFakturerbar ? "bg-primary" : "bg-black/20"
}`}
onClick={handleToggleChange}
>
<div
className={`m-[2px] bg-white rounded-full w-6 h-6 ${
isFakturerbar && " translate-x-6"
}`}
></div>
</div>
</label>
</>
)}
</div>

<ActionButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export function DetailedEngagementModalCell({
return (
<td className="h-8 p-0.5">
<div
className={`flex justify-end items-center bg-offer/30 border rounded-lg h-full ${
className={`flex justify-end items-center border rounded-lg h-full ${
hours == 0 && "bg-opacity-30"
} ${getColorByStaffingType(
getBookingTypeFromProjectState(project?.bookingType) ??
Expand Down Expand Up @@ -184,8 +184,8 @@ export function DetailedEngagementModalCell({
setIsDisabledHotkeys(false);
}}
onDragStart={() => {
setHourDragValue(hours),
setStartDragWeek(dayToWeek(firstDayInWeek));
setHourDragValue(hours);
setStartDragWeek(dayToWeek(firstDayInWeek));
}}
onDragEnterCapture={() => {
setCurrentDragWeek(dayToWeek(firstDayInWeek));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EngagementState, BookingType, ConsultantReadModel } from "@/api-types";
import { BookingType, ConsultantReadModel, EngagementState } from "@/api-types";
import { ConsultantWithWeekHours } from "@/types";
import { DateTime } from "luxon";

Expand All @@ -12,6 +12,8 @@ export function getBookingTypeFromProjectState(projectState?: EngagementState) {
return BookingType.Booking;
case EngagementState.Offer:
return BookingType.Offer;
case EngagementState.Absence:
return BookingType.PlannedAbsence;
default:
return BookingType.Offer;
}
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/components/Staffing/helpers/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { BookingType, EngagementState } from "@/api-types";
import {
BookingType,
ConsultantReadModel,
DetailedBooking,
EngagementState,
} from "@/api-types";
import React, { ReactElement } from "react";
import { Briefcase, Coffee, FileText, Moon, Sun } from "react-feather";
import { InfoPillVariant } from "@/components/Staffing/InfoPill";
import { ConsultantReadModel, DetailedBooking } from "@/api-types";

export function getColorByStaffingType(type: BookingType): string {
switch (type) {
Expand Down Expand Up @@ -30,6 +34,8 @@ export function getIconByProjectState(
return <FileText size={size} className="text-primary_darker" />;
case EngagementState.Order:
return <Briefcase size={size} className="text-black" />;
case EngagementState.Absence:
return <Moon size={size} className="text-absence_darker" />;
default:
return <></>;
}
Expand All @@ -41,6 +47,8 @@ export function getColorByProjectState(type?: EngagementState): string {
return "bg-offer";
case EngagementState.Order:
return "bg-primary/[3%]";
case EngagementState.Absence:
return "bg-absence";
default:
return "";
}
Expand Down

0 comments on commit 6a1d23b

Please sign in to comment.