Skip to content

Commit

Permalink
locking milestones (#1068)
Browse files Browse the repository at this point in the history
  • Loading branch information
dinesh-aot authored Oct 19, 2023
1 parent 8af63db commit e75f849
Show file tree
Hide file tree
Showing 11 changed files with 87 additions and 34 deletions.
2 changes: 1 addition & 1 deletion epictrack-api/src/api/resources/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def get(event_id):
def delete(event_id):
"""Endpoint to delete a milestone event"""
req.MilestoneEventPathParameterSchema().load(request.view_args)
message = EventService.delete_milestone(event_id)
message = EventService.delete_event(event_id)
return message, HTTPStatus.OK


Expand Down
8 changes: 7 additions & 1 deletion epictrack-api/src/api/schemas/request/event_request.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Event resource's input validations"""
from marshmallow import fields, validate
from marshmallow import fields, validate, pre_load

from api.schemas.request.custom_fields import IntegerList

Expand Down Expand Up @@ -87,6 +87,12 @@ class MilestoneEventBodyParameterSchema(RequestBodyParameterSchema):
allow_none=True
)

@pre_load
def translate_empty_date_strings(self, data, *args, **kwargs): # pylint: disable=unused-argument
"""Translate empty date string to None"""
data['actual_date'] = data.get('actual_date') or None
return data


class MilestoneEventPathParameterSchema(RequestPathParameterSchema):
"""Milestone event path parameter schema"""
Expand Down
9 changes: 7 additions & 2 deletions epictrack-api/src/api/services/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,10 +539,15 @@ def bulk_delete_milestones(cls, milestone_ids: List):
return "Deleted successfully"

@classmethod
def delete_milestone(cls, milestone_id: int):
def delete_event(cls, event_id: int):
"""Mark milestone as deleted by id"""
event = Event.find_by_id(event_id)
if not event:
raise ResourceNotFoundError("No event found with given id")
if event.actual_date:
raise UnprocessableEntityError("Locked events cannot be deleted")
db.session.query(Event).filter(
or_(Event.id == milestone_id, Event.source_event_id == milestone_id)
or_(Event.id == event_id, Event.source_event_id == event_id)
).update({"is_active": False, "is_deleted": True})
db.session.commit()
return "Deleted successfully"
2 changes: 1 addition & 1 deletion epictrack-web/src/components/shared/CustomSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const CustomSwitch = styled((props: SwitchProps) => (
border: "6px solid #fff",
},
"&.Mui-disabled .MuiSwitch-thumb": {
color: "red",
color: Palette.white,
},
},
"& .MuiSwitch-track": {
Expand Down
56 changes: 35 additions & 21 deletions epictrack-web/src/components/workPlan/event/EventForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ import outcomeConfigurationService from "../../../services/outcomeConfigurationS
import DecisionInput from "./components/DecisionInput";
import { POSITION_ENUM } from "../../../models/position";

interface TaskFormProps {
interface EventFormProps {
onSave: () => void;
event?: MilestoneEvent;
isFormFieldsLocked: boolean;
}
interface NumberOfDaysChangeProps {
numberOfDays?: number | undefined;
Expand All @@ -55,7 +56,7 @@ interface NumberOfDaysChangeProps {
}
const InfoIcon: React.FC<IconProps> = Icons["InfoIcon"];

const EventForm = ({ onSave, event }: TaskFormProps) => {
const EventForm = ({ onSave, event, isFormFieldsLocked }: EventFormProps) => {
const [submittedEvent, setSubmittedEvent] = React.useState<MilestoneEvent>();
const [configurations, setConfigurations] = React.useState<
EventConfiguration[]
Expand All @@ -75,7 +76,6 @@ const EventForm = ({ onSave, event }: TaskFormProps) => {
const [anticipatedLabel, setAnticipatedLabel] =
React.useState("Anticipated Date");
const [actualDateLabel, setActualDateLabel] = React.useState("Actual Date");
const [outcomes, setOutcomes] = React.useState<ListType[]>([]);
const titleRef = React.useRef();
const schema = React.useMemo(
() =>
Expand All @@ -91,18 +91,26 @@ const EventForm = ({ onSave, event }: TaskFormProps) => {
otherwise: () => yup.string().nullable(),
}),
outcome_id: yup.string().when([], {
is: () => actualAdded,
is: () =>
actualAdded &&
selectedConfiguration?.event_category_id === EventCategory.DECISION,
then: () => yup.string().required("Please select the decision"),
otherwise: () => yup.string().nullable(),
}),
decision_maker_id: yup.string().when([], {
is: () => actualAdded,
is: () =>
actualAdded &&
selectedConfiguration?.event_category_id === EventCategory.DECISION,
then: () => yup.string().required("Please select the decision maker"),
otherwise: () => yup.string().nullable(),
}),
}),
[selectedConfiguration, outcomes, actualAdded]
[selectedConfiguration, actualAdded]
);
// const isFormFieldsLocked = React.useMemo<boolean>(() => {
// const isLocked = !!event?.actual_date;
// return isLocked;
// }, [event]);
const methods = useForm({
resolver: yupResolver(schema),
defaultValues: event,
Expand All @@ -115,6 +123,7 @@ const EventForm = ({ onSave, event }: TaskFormProps) => {
formState: { errors },
reset,
control,
getValues,
} = methods;

React.useEffect(() => {
Expand Down Expand Up @@ -182,12 +191,10 @@ const EventForm = ({ onSave, event }: TaskFormProps) => {
data.actual_date = Moment(data.actual_date).format();
}
data.notes = notes;
setSubmittedEvent(data);
// setSubmittedEvent(data);
const showConfirmDialog =
(event === undefined && data.actual_date != null) ||
(event != null &&
event.actual_date == null &&
data.actual_date != null);
(event === undefined && data.actual_date != "") ||
(event != null && event.actual_date == null && data.actual_date != "");
if (!showConfirmDialog) {
saveEvent(data);
}
Expand All @@ -206,7 +213,7 @@ const EventForm = ({ onSave, event }: TaskFormProps) => {

const saveEvent = React.useCallback(
async (data?: MilestoneEvent) => {
const dataToBeSubmitted = data || submittedEvent;
const dataToBeSubmitted = data || getValues();
if (event) {
const createResult = await eventService.update(
dataToBeSubmitted,
Expand Down Expand Up @@ -303,7 +310,7 @@ const EventForm = ({ onSave, event }: TaskFormProps) => {
options={configurations || []}
getOptionValue={(o: ListType) => o.id.toString()}
getOptionLabel={(o: ListType) => o.name}
disabled={!!event}
disabled={!!event || isFormFieldsLocked}
onHandleChange={(configuration_id) =>
onChangeMilestoneType(configuration_id)
}
Expand All @@ -320,6 +327,7 @@ const EventForm = ({ onSave, event }: TaskFormProps) => {
<TextField
fullWidth
placeholder="Title"
disabled={isFormFieldsLocked}
defaultValue={event?.name}
error={!!errors?.name?.message}
inputRef={titleRef}
Expand Down Expand Up @@ -348,6 +356,7 @@ const EventForm = ({ onSave, event }: TaskFormProps) => {
sx={{
mr: "2px",
}}
disabled={isFormFieldsLocked}
control={
<ControlledSwitch
{...register("high_priority")}
Expand Down Expand Up @@ -375,6 +384,7 @@ const EventForm = ({ onSave, event }: TaskFormProps) => {
}) => (
<LocalizationProvider dateAdapter={AdapterDayjs} for>
<DatePicker
disabled={isFormFieldsLocked}
format={DATE_FORMAT}
slotProps={{
textField: {
Expand All @@ -389,9 +399,10 @@ const EventForm = ({ onSave, event }: TaskFormProps) => {
}}
value={dayjs(value)}
onChange={(event: any) => {
onChange(event);
const d = event ? event["$d"] : null;
onChange(d);
daysOnChangeHandler({
anticipatedDate: event["$d"],
anticipatedDate: d,
});
}}
defaultValue={dayjs(
Expand All @@ -408,12 +419,16 @@ const EventForm = ({ onSave, event }: TaskFormProps) => {
<Controller
name="actual_date"
control={control}
defaultValue={
event?.actual_date ? Moment(event?.actual_date).format() : ""
}
render={({
field: { onChange, value },
fieldState: { error },
}) => (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
disabled={isFormFieldsLocked}
format={DATE_FORMAT}
slotProps={{
textField: {
Expand All @@ -433,9 +448,6 @@ const EventForm = ({ onSave, event }: TaskFormProps) => {
actualDate: d,
});
}}
defaultValue={
event?.actual_date ? dayjs(event?.actual_date) : ""
}
sx={{ display: "block" }}
/>
</LocalizationProvider>
Expand All @@ -457,24 +469,26 @@ const EventForm = ({ onSave, event }: TaskFormProps) => {
{selectedConfiguration?.multiple_days && (
<MultiDaysInput
endDateRef={endDateRef}
isFormFieldsLocked={isFormFieldsLocked}
numberOfDaysRef={numberOfDaysRef}
onChangeDay={daysOnChangeHandler}
/>
)}
{[EventCategory.EXTENSION, EventCategory.SUSPENSION].includes(
Number(selectedConfiguration?.event_category_id)
) && <ExtSusInput />}
) && <ExtSusInput isFormFieldsLocked={isFormFieldsLocked} />}
{selectedConfiguration?.event_category_id === EventCategory.PCP &&
![EventType.OPEN_HOUSE, EventType.VIRTUAL_OPEN_HOUSE].includes(
selectedConfiguration?.event_type_id
) && <PCPInput />}
) && <PCPInput isFormFieldsLocked={isFormFieldsLocked} />}
{[EventType.OPEN_HOUSE, EventType.VIRTUAL_OPEN_HOUSE].includes(
Number(selectedConfiguration?.event_type_id)
) && <SingleDayPCPInput />}
) && <SingleDayPCPInput isFormFieldsLocked={isFormFieldsLocked} />}
{actualAdded &&
selectedConfiguration?.event_category_id ===
EventCategory.DECISION && (
<DecisionInput
isFormFieldsLocked={isFormFieldsLocked}
configurationId={selectedConfiguration.id}
decisionMakerPositionId={
ctx.work?.decision_maker_position_id ||
Expand Down
17 changes: 12 additions & 5 deletions epictrack-web/src/components/workPlan/event/EventList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ const EventList = () => {
React.useState<boolean>(false);
const [showDeleteDialog, setShowDeleteDialog] =
React.useState<boolean>(false);

const isEventFormFieldLocked = React.useMemo(() => {
return !!milestoneEvent?.actual_date;
}, [milestoneEvent]);
React.useEffect(() => setEvents([]), [ctx.selectedWorkPhase?.phase.id]);
React.useEffect(() => {
getCombinedEvents();
Expand Down Expand Up @@ -151,8 +153,9 @@ const EventList = () => {
data.forEach((array: EventsGridModel[]) => {
result = result.concat(array);
});
result = result.sort((a, b) =>
Moment(a.start_date).diff(b.start_date, "seconds")
result = result.sort(
(a, b) =>
Moment(a.start_date).diff(b.start_date, "seconds") || a.id - b.id
);
setEvents(result);
}
Expand Down Expand Up @@ -709,7 +712,6 @@ const EventList = () => {
}
setShowDeleteDialog(false);
};
console.log("EVENTS", events);
const deleteAction = (
<>
{showDeleteMilestoneButton && (
Expand All @@ -730,6 +732,7 @@ const EventList = () => {
sx={{
border: `2px solid ${Palette.white}`,
}}
disabled={isEventFormFieldLocked}
onClick={() => setShowDeleteDialog(true)}
>
Delete
Expand Down Expand Up @@ -921,7 +924,11 @@ const EventList = () => {
}}
additionalActions={deleteAction}
>
<EventForm onSave={onDialogClose} event={milestoneEvent} />
<EventForm
onSave={onDialogClose}
event={milestoneEvent}
isFormFieldsLocked={isEventFormFieldLocked}
/>
</TrackDialog>
<TrackDialog
open={showTemplateForm}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import { Staff } from "../../../../models/staff";
export interface DecisionInputProps {
configurationId: number;
decisionMakerPositionId: number;
isFormFieldsLocked: boolean;
}
const DecisionInput = ({
decisionMakerPositionId,
configurationId,
isFormFieldsLocked,
}: DecisionInputProps) => {
const [decisionMakers, setDecisionMakers] = React.useState<Staff[]>([]);
const [outcomes, setOutcomes] = React.useState<ListType[]>([]);
Expand Down Expand Up @@ -67,6 +69,7 @@ const DecisionInput = ({
<Grid item xs={6}>
<ETFormLabel required>Decision Maker</ETFormLabel>
<ControlledSelectV2
disabled={isFormFieldsLocked}
helperText={errors?.decision_maker_id?.message?.toString()}
options={decisionMakers || []}
getOptionValue={(o: Staff) => o.id.toString()}
Expand All @@ -77,6 +80,7 @@ const DecisionInput = ({
<Grid item xs={6}>
<ETFormLabel required>Decision</ETFormLabel>
<ControlledSelectV2
disabled={isFormFieldsLocked}
helperText={errors?.outcome_id?.message?.toString()}
options={outcomes || []}
getOptionValue={(o: ListType) => o.id.toString()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import ControlledSelectV2 from "../../../shared/controlledInputComponents/Contro
import { useFormContext } from "react-hook-form";
import { ListType } from "../../../../models/code";

const ExtSusInput = () => {
interface ExtSusInputProps {
isFormFieldsLocked: boolean;
}
const ExtSusInput = ({ isFormFieldsLocked }: ExtSusInputProps) => {
const [actSections, setActSections] = React.useState<ListType[]>([]);
const [reasonCount, setReasonCount] = React.useState<number>(0);
const changeReasonTextHandler = (event: any) => {
Expand All @@ -27,6 +30,7 @@ const ExtSusInput = () => {
<Grid item xs={12}>
<ETFormLabel required>Act Section</ETFormLabel>
<ControlledSelectV2
disabled={isFormFieldsLocked}
helperText={errors?.act_section_id?.message?.toString()}
options={actSections || []}
getOptionValue={(o: ListType) => o.id.toString()}
Expand All @@ -44,6 +48,7 @@ const ExtSusInput = () => {
<TextField
fullWidth
multiline
disabled={isFormFieldsLocked}
rows={3}
InputProps={{
inputProps: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ export interface MultiDaysInputProps {
numberOfDaysRef: React.MutableRefObject<any>;
endDateRef: React.MutableRefObject<any>;
onChangeDay: () => void;
isFormFieldsLocked: boolean;
}
const MultiDaysInput = ({
endDateRef,
numberOfDaysRef,
onChangeDay,
isFormFieldsLocked,
}: MultiDaysInputProps) => {
const {
unregister,
Expand All @@ -30,6 +32,7 @@ const MultiDaysInput = ({
<ETFormLabel required>Number of Days</ETFormLabel>
<TextField
fullWidth
disabled={isFormFieldsLocked}
inputRef={numberOfDaysRef}
helperText={errors?.number_of_days?.message?.toString()}
error={!!errors?.number_of_days?.message}
Expand Down
Loading

0 comments on commit e75f849

Please sign in to comment.