Skip to content

Commit

Permalink
Merge pull request #180 from jadmsaadaot/SUBMIT-task#176-C
Browse files Browse the repository at this point in the history
Integrate review component with api
  • Loading branch information
jadmsaadaot authored Nov 27, 2024
2 parents f4bd121 + 1b31f65 commit 506d2a3
Show file tree
Hide file tree
Showing 16 changed files with 333 additions and 111 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
""" Add status and active columns to submission_reviews
Revision ID: 7941b44f6927
Revises: 87706710bc7c
Create Date: 2024-11-26 13:46:10.424276
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '7941b44f6927'
down_revision = '87706710bc7c'
branch_labels = None
depends_on = None


def upgrade():
# Create the enum type
submissionreviewstatus_enum = sa.Enum('PENDING_STAFF_REVIEW', 'PENDING_MANAGER_REVIEW', 'APPROVED', 'REJECTED', name='submissionreviewstatus')
submissionreviewstatus_enum.create(op.get_bind(), checkfirst=True)

# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('submission_reviews', schema=None) as batch_op:
batch_op.add_column(sa.Column('status', submissionreviewstatus_enum, nullable=False))
batch_op.add_column(sa.Column('active', sa.Boolean(), nullable=False))
batch_op.drop_constraint('submission_reviews_item_id_key', type_='unique')
# ### end Alembic commands ###

def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('submission_reviews', schema=None) as batch_op:
batch_op.create_unique_constraint('submission_reviews_item_id_key', ['item_id'])
batch_op.drop_column('active')
batch_op.drop_column('status')

# Drop the enum type
submissionreviewstatus_enum = sa.Enum('PENDING_STAFF_REVIEW', 'PENDING_MANAGER_REVIEW', 'APPROVED', 'REJECTED', name='submissionreviewstatus')
submissionreviewstatus_enum.drop(op.get_bind(), checkfirst=True)
# ### end Alembic commands ###
2 changes: 1 addition & 1 deletion submit-api/src/submit_api/resources/staff/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,5 @@ class ItemReview(Resource):
def post(item_id):
"""Save submission review."""
request_body = SaveSubmissionReviewRequestSchema().load(API.payload)
review, _ = ItemService.save_submission_review(item_id, request_body)
review = ItemService.save_submission_review(item_id, request_body)
return SubmissionReviewSchema().dump(review), HTTPStatus.OK
3 changes: 2 additions & 1 deletion submit-api/src/submit_api/schemas/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from submit_api.schemas.internal_staff_document import InternalStaffDocument
from submit_api.schemas.item_type import ItemTypeSchema
from submit_api.schemas.submission import SubmittedDocumentSchema, SubmittedFormSchema
from submit_api.schemas.submission_review import SubmissionReviewSchema


class ItemSubmissionSchema(Schema):
Expand Down Expand Up @@ -68,4 +69,4 @@ class Meta: # pylint: disable=too-few-public-methods
unknown = EXCLUDE

internal_staff_documents = fields.Nested(InternalStaffDocument, data_key="internal_staff_documents", many=True)
review = fields.Str(data_key="review")
review = fields.Nested(SubmissionReviewSchema, data_key="review")
20 changes: 14 additions & 6 deletions submit-api/src/submit_api/services/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class ItemService:
@classmethod
def get_item_by_id(cls, item_id) -> ItemModel:
"""Get item by id."""
item = cls.get_item_by_id(item_id)
item = ItemModel.find_by_id(item_id)
if not item:
raise ValueError(f"Item with id {item_id} not found.")
return item
Expand Down Expand Up @@ -54,7 +54,7 @@ def update_submission_item(cls, item_id, update_data):
def get_or_create_active_item_review(cls, item_id) -> SubmissionReview:
"""Get item by id."""
_ = cls.get_item_by_id(item_id)
review = SubmissionReview.get_by_item_id(item_id)
review = SubmissionReview.get_active_review_by_item_id(item_id)
if not review:
review = SubmissionReview(item_id=item_id)
return review
Expand All @@ -63,7 +63,12 @@ def get_or_create_active_item_review(cls, item_id) -> SubmissionReview:
def _save_submission_review_answers(cls, review, review_data):
"""Save submission item review answers."""
form_answers = review_data.get('form_answers', {})
review.form_answers.update(form_answers)

current_form_answers = review.form_answers if review.form_answers else {}
review.form_answers = {
**current_form_answers,
**form_answers,
}
return review

@classmethod
Expand All @@ -90,11 +95,12 @@ def _get_review_status_processor(cls, status) -> callable:
return status_processor_map[status]

@classmethod
def process_review_status(cls, review, status, session):
def process_review_status(cls, review, status):
"""Process review status."""
if not status:
return
status_processor = cls._get_review_status_processor(status)
status_processor(review, status)
session.add(review)

@classmethod
def save_submission_review(cls, item_id, review_data):
Expand All @@ -103,7 +109,9 @@ def save_submission_review(cls, item_id, review_data):

with session_scope() as session:
cls._save_submission_review_answers(review, review_data)
cls.process_review_status(review, review_data['status'], session)
status = review_data.get('status')
cls.process_review_status(review, status)
session.add(review)
session.flush()
session.commit()
return review
10 changes: 1 addition & 9 deletions submit-web/src/components/Shared/CustomRadioOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@ type CustomRadioOptionsProps = {
options: Option[];
error?: boolean;
disabled?: boolean;
onChange?: (value: string | number | boolean) => void;
};

export const CustomRadioOptions = ({
options,
error = false,
disabled = false,
onChange,
}: CustomRadioOptionsProps) => {
const sx = [
disabled && {
Expand All @@ -34,13 +32,7 @@ export const CustomRadioOptions = ({
<FormControlLabel
key={value.toString()}
value={value}
control={
<Radio
sx={sx}
disabled={disabled}
onChange={() => onChange && onChange(value)}
/>
}
control={<Radio sx={sx} disabled={disabled} />}
label={label}
/>
))}
Expand Down
32 changes: 32 additions & 0 deletions submit-web/src/components/Shared/SubmitRadio.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { FormControlLabel, Radio } from "@mui/material";
import { BCDesignTokens } from "epic.theme";

type SubmitRadioProps = {
value: string | number | boolean;
label: string;
error?: boolean;
disabled?: boolean;
};
export const SubmitRadio = ({
value,
label,
error = false,
disabled = false,
}: SubmitRadioProps) => {
const sx = [
disabled && {
color: `${BCDesignTokens.typographyColorDisabled} !important`,
},
error && {
color: BCDesignTokens.surfaceColorPrimaryDangerButtonDefault,
},
];

return (
<FormControlLabel
value={value}
control={<Radio sx={sx} disabled={disabled} />}
label={label}
/>
);
};
24 changes: 3 additions & 21 deletions submit-web/src/components/Shared/YesNoRadioOptions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { FormControlLabel, Radio } from "@mui/material";
import { BCDesignTokens } from "epic.theme";
import { SubmitRadio } from "./SubmitRadio";

export const YES = true;
export const NO = false;
Expand All @@ -12,27 +11,10 @@ export const YesNoRadioOptions = ({
error = true,
disabled = false,
}: IYesNoRadioOptionsProps) => {
const sx = [
disabled && {
color: `${BCDesignTokens.typographyColorDisabled} !important`,
},
error && {
color: BCDesignTokens.surfaceColorPrimaryDangerButtonDefault,
},
];

return (
<>
<FormControlLabel
value={YES}
control={<Radio sx={sx} disabled={disabled} />}
label="Yes"
/>
<FormControlLabel
value={NO}
control={<Radio sx={sx} disabled={disabled} />}
label="No"
/>
<SubmitRadio value={YES} label="Yes" error={error} disabled={disabled} />
<SubmitRadio value={NO} label="No" error={error} disabled={disabled} />
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { FC } from "react";
import React, { FC } from "react";
import {
FormControl,
FormHelperText,
RadioGroup,
RadioGroupProps,
} from "@mui/material";
import { Controller, useFormContext } from "react-hook-form";
import { get } from "lodash";

type IFormInputProps = {
name: string;
Expand All @@ -21,18 +22,28 @@ const ControlledRadioGroup: FC<IFormInputProps> = ({
formState: { defaultValues, errors },
} = useFormContext();

const error = errors[name];
const error = get(errors, name);
// pass error prop to children
const childrenWithProps = React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { error: Boolean(error) } as any); // eslint-disable-line @typescript-eslint/no-explicit-any
}
return child;
});

return (
<FormControl error={Boolean(error)}>
<Controller
control={control}
name={name}
defaultValue={defaultValues?.[name] || ""}
render={({ field }) => (
<RadioGroup {...otherProps} {...field}>
{children}
</RadioGroup>
)}
render={({ field }) => {
return (
<RadioGroup {...otherProps} {...field}>
{childrenWithProps}
</RadioGroup>
);
}}
/>
{error && <FormHelperText>{error.message?.toString()}</FormHelperText>}
</FormControl>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,96 @@
import { Button, Grid } from "@mui/material";
import { useSaveSubmissionReview } from "@/hooks/api/useItems";
import { Grid } from "@mui/material";
import { useParams } from "@tanstack/react-router";
import { useFormContext } from "react-hook-form";
import { useState } from "react";
import { LoadingButton } from "@/components/Shared/LoadingButton";
import { SUBMISSION_REVIEW_STATUS } from "@/models/SubmissionReview";
import { isAxiosError } from "axios";
import { notify } from "@/components/Shared/Snackbar/snackbarStore";
import { consultationSchema } from "./constants";

export default function ActionButtons({
saveAndClose,
}: {
saveAndClose: () => void;
}) {
export default function ActionButtons() {
const {
projectId,
submissionPackageId,
submissionId: submissionItemId,
} = useParams({
from: "/staff/_staffLayout/projects/$projectId/_projectLayout/submission-packages/$submissionPackageId/_submissionLayout/submissions/$submissionId",
});

const { mutateAsync: saveSubmissionReview } = useSaveSubmissionReview({
itemId: Number(submissionItemId),
packageId: Number(submissionPackageId),
accountProjectId: Number(projectId),
});
const [isSavingAndClosing, setIsSavingAndClosing] = useState(false);
const [isSendingToManager, setIsSendingToManager] = useState(false);

const isLoading = isSavingAndClosing || isSendingToManager;

const { getValues, trigger } = useFormContext();

const handleSaveAndClose = async () => {
// Add save logic here
const role = "staff";
const validateAtKey = role === "staff" ? "staff" : "manager";
const data = getValues();
try {
const validData = consultationSchema.validateSyncAt(validateAtKey, data);
const requestBody = {
form_answers: {
[validateAtKey]: validData,
},
};
setIsSavingAndClosing(true);
await saveSubmissionReview(requestBody);
setIsSavingAndClosing(false);
} catch (error) {
trigger();
setIsSavingAndClosing(false);
if (isAxiosError(error)) {
notify.error("Failed to save review");
}
}
};
const handleSendToManager = async () => {
try {
setIsSendingToManager(true);
const validData = consultationSchema.validateSyncAt("staff", getValues());
const requestBody = {
status: SUBMISSION_REVIEW_STATUS.PENDING_MANAGER_REVIEW,
form_answers: validData,
};
await saveSubmissionReview(requestBody);
setIsSendingToManager(false);
} catch (error) {
setIsSendingToManager(false);
trigger();
if (isAxiosError(error)) {
notify.error("Failed to send recommendations to manager");
}
}
};
return (
<Grid item xs={12} container spacing={2}>
<Grid item xs={12} sm="auto">
<Button color="secondary" onClick={saveAndClose}>
<LoadingButton
color="secondary"
onClick={handleSaveAndClose}
disabled={isLoading}
loading={isSavingAndClosing}
>
Save & Exit
</Button>
</LoadingButton>
</Grid>
<Grid item xs={12} sm="auto">
<Button type="submit">Send Recommendations to Manager</Button>
<LoadingButton
disabled={isLoading}
loading={isSendingToManager}
onClick={handleSendToManager}
>
Send Recommendations to Manager
</LoadingButton>
</Grid>
</Grid>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const defaultFormData = {
};

interface FormFieldSectionProps {
formData: ConsultationRecordForm; // Replace FormValues with your actual form schema interface
formData: Partial<ConsultationRecordForm>; // Replace FormValues with your actual form schema interface
}

export default function FormFieldSection({
Expand Down
Loading

0 comments on commit 506d2a3

Please sign in to comment.