From a80cca883a27127f7432404459eb57c3d492901e Mon Sep 17 00:00:00 2001 From: Dinesh Date: Tue, 17 Oct 2023 20:16:45 -0700 Subject: [PATCH 01/10] workphase integration in task events --- .../47b41964f6df_work_phase_in_task_events.py | 58 +++++++++++++++++++ epictrack-api/src/api/models/task_event.py | 6 +- epictrack-api/src/api/resources/task.py | 5 +- .../src/api/schemas/request/task_request.py | 30 ++-------- epictrack-api/src/api/services/task.py | 38 +++++------- .../components/workPlan/event/EventList.tsx | 15 ++--- .../src/components/workPlan/task/TaskForm.tsx | 3 +- epictrack-web/src/models/project.ts | 2 - epictrack-web/src/models/taskEvent.ts | 3 +- .../taskEventService/taskEventService.ts | 4 +- 10 files changed, 92 insertions(+), 72 deletions(-) create mode 100644 epictrack-api/migrations/versions/47b41964f6df_work_phase_in_task_events.py diff --git a/epictrack-api/migrations/versions/47b41964f6df_work_phase_in_task_events.py b/epictrack-api/migrations/versions/47b41964f6df_work_phase_in_task_events.py new file mode 100644 index 000000000..c854d1432 --- /dev/null +++ b/epictrack-api/migrations/versions/47b41964f6df_work_phase_in_task_events.py @@ -0,0 +1,58 @@ +"""work_phase in task events + +Revision ID: 47b41964f6df +Revises: f1ed0759bf24 +Create Date: 2023-10-17 18:13:13.630485 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '47b41964f6df' +down_revision = 'f1ed0759bf24' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('task_events', schema=None) as batch_op: + batch_op.add_column(sa.Column('work_phase_id', sa.Integer(), nullable=True)) + batch_op.drop_constraint('task_events_work_id_fkey', type_='foreignkey') + batch_op.drop_constraint('task_events_phase_id_fkey', type_='foreignkey') + batch_op.create_foreign_key(None, 'work_phases', ['work_phase_id'], ['id']) + batch_op.drop_column('work_id') + batch_op.drop_column('phase_id') + + with op.batch_alter_table('task_events_history', schema=None) as batch_op: + batch_op.add_column(sa.Column('work_phase_id', sa.Integer(), autoincrement=False, nullable=True)) + batch_op.drop_constraint('task_events_history_phase_id_fkey', type_='foreignkey') + batch_op.drop_constraint('task_events_history_work_id_fkey', type_='foreignkey') + batch_op.create_foreign_key(None, 'work_phases', ['work_phase_id'], ['id']) + batch_op.drop_column('work_id') + batch_op.drop_column('phase_id') + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('task_events_history', schema=None) as batch_op: + batch_op.add_column(sa.Column('phase_id', sa.INTEGER(), autoincrement=False, nullable=False)) + batch_op.add_column(sa.Column('work_id', sa.INTEGER(), autoincrement=False, nullable=False)) + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.create_foreign_key('task_events_history_work_id_fkey', 'works', ['work_id'], ['id']) + batch_op.create_foreign_key('task_events_history_phase_id_fkey', 'phase_codes', ['phase_id'], ['id']) + batch_op.drop_column('work_phase_id') + + with op.batch_alter_table('task_events', schema=None) as batch_op: + batch_op.add_column(sa.Column('phase_id', sa.INTEGER(), autoincrement=False, nullable=False)) + batch_op.add_column(sa.Column('work_id', sa.INTEGER(), autoincrement=False, nullable=False)) + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.create_foreign_key('task_events_phase_id_fkey', 'phase_codes', ['phase_id'], ['id']) + batch_op.create_foreign_key('task_events_work_id_fkey', 'works', ['work_id'], ['id']) + batch_op.drop_column('work_phase_id') + + # ### end Alembic commands ### diff --git a/epictrack-api/src/api/models/task_event.py b/epictrack-api/src/api/models/task_event.py index c69ad260a..a8affb562 100644 --- a/epictrack-api/src/api/models/task_event.py +++ b/epictrack-api/src/api/models/task_event.py @@ -48,8 +48,7 @@ class TaskEvent(BaseModelVersioned): sa.Integer, primary_key=True, autoincrement=True ) # TODO check how it can be inherited from parent name = sa.Column(sa.String) - work_id = sa.Column(sa.Integer, sa.ForeignKey("works.id"), nullable=False) - phase_id = sa.Column(sa.Integer, sa.ForeignKey("phase_codes.id"), nullable=False) + work_phase_id = sa.Column(sa.ForeignKey('work_phases.id'), nullable=True) start_date = sa.Column(sa.DateTime(timezone=True)) number_of_days = sa.Column(sa.Integer, default=1, nullable=False) @@ -57,8 +56,7 @@ class TaskEvent(BaseModelVersioned): notes = sa.Column(sa.String) status = sa.Column(sa.Enum(StatusEnum), default=StatusEnum.NOT_STARTED) - phase = relationship("PhaseCode", foreign_keys=[phase_id], lazy="select") - work = relationship("Work", foreign_keys=[work_id], lazy="select") + work_phase = relationship('WorkPhase', foreign_keys=[work_phase_id], lazy='select') assignees = relationship( "TaskEventAssignee", diff --git a/epictrack-api/src/api/resources/task.py b/epictrack-api/src/api/resources/task.py index 28e44a77a..2da705af9 100644 --- a/epictrack-api/src/api/resources/task.py +++ b/epictrack-api/src/api/resources/task.py @@ -37,9 +37,8 @@ class Events(Resource): def get(): """Return all task templates.""" args = req.TaskEventQueryParamSchema().load(request.args) - work_id = args.get("work_id") - phase_id = args.get("phase_id") - task_events = TaskService.find_task_events(work_id, phase_id) + work_phase_id = args.get("work_phase_id") + task_events = TaskService.find_task_events(work_phase_id) return ( jsonify(res.TaskEventResponseSchema(many=True).dump(task_events)), HTTPStatus.OK, diff --git a/epictrack-api/src/api/schemas/request/task_request.py b/epictrack-api/src/api/schemas/request/task_request.py index e38d5fe4e..ace33d03f 100644 --- a/epictrack-api/src/api/schemas/request/task_request.py +++ b/epictrack-api/src/api/schemas/request/task_request.py @@ -133,14 +133,8 @@ class TaskTemplateIdPathParameterSchema(RequestPathParameterSchema): class TaskEventQueryParamSchema(RequestQueryParameterSchema): """Task events per work/phase query parameters""" - work_id = fields.Int( - metadata={"description": "Work ID for the events"}, - validate=validate.Range(min=1), - required=True, - ) - - phase_id = fields.Int( - metadata={"description": "Phase ID for the events"}, + work_phase_id = fields.Int( + metadata={"description": "Work phase ID of the event configuration"}, validate=validate.Range(min=1), required=True, ) @@ -154,14 +148,8 @@ class TaskEventBodyParamSchema(RequestBodyParameterSchema): required=True, ) - work_id = fields.Int( - metadata={"description": "Id of work"}, - validate=validate.Range(min=1), - required=True, - ) - - phase_id = fields.Int( - metadata={"description": "Id of the phase"}, + work_phase_id = fields.Int( + metadata={"description": "Work phase ID of the event configuration"}, validate=validate.Range(min=1), required=True, ) @@ -210,14 +198,8 @@ class TaskEventIdPathParameterSchema(RequestPathParameterSchema): class TaskTemplateImportEventsBodyParamSchema(RequestBodyParameterSchema): """Task template import events schema""" - work_id = fields.Int( - metadata={"description": "Id of work"}, - validate=validate.Range(min=1), - required=True, - ) - - phase_id = fields.Int( - metadata={"description": "Id of the phase"}, + work_phase_id = fields.Int( + metadata={"description": "Work phase ID of the event configuration"}, validate=validate.Range(min=1), required=True, ) diff --git a/epictrack-api/src/api/services/task.py b/epictrack-api/src/api/services/task.py index 2a7645b3b..310f45a1b 100644 --- a/epictrack-api/src/api/services/task.py +++ b/epictrack-api/src/api/services/task.py @@ -21,11 +21,10 @@ from sqlalchemy.orm import contains_eager, lazyload from api.exceptions import UnprocessableEntityError -from api.models import StaffWorkRole, StatusEnum, TaskEvent, TaskEventAssignee, db +from api.models import StaffWorkRole, StatusEnum, TaskEvent, TaskEventAssignee, WorkPhase, db from api.models.task_event_responsibility import TaskEventResponsibility from .task_template import TaskTemplateService -from .work_phase import WorkPhaseService class TaskService: @@ -35,10 +34,9 @@ class TaskService: def create_task_event(cls, data: dict, commit: bool = True) -> TaskEvent: """Create task event""" task_event = TaskEvent(**cls._prepare_task_event_object(data)) - # if cls._get_task_count(task_event.work_id, task_event.phase_id) > 0: - # raise UnprocessableEntityError("Maxium task count per phase has reached") + work_phase = WorkPhase.find_by_id(data.get("work_phase_id")) if data.get("assignee_ids") and not cls._validate_assignees( - data.get("assignee_ids"), data + data.get("assignee_ids"), work_phase.work_id ): raise UnprocessableEntityError( "Only team members can be assigned to a task" @@ -50,9 +48,6 @@ def create_task_event(cls, data: dict, commit: bool = True) -> TaskEvent: cls._handle_responsibilities( data.get("responsibility_ids"), [task_event.id] ) - work_phase = WorkPhaseService.find_by_work_nd_phase( - data.get("work_id"), data.get("phase_id") - ) work_phase.task_added = True if commit: db.session.commit() @@ -62,8 +57,9 @@ def create_task_event(cls, data: dict, commit: bool = True) -> TaskEvent: def update_task_event(cls, data: dict, task_event_id) -> TaskEvent: """Update task event""" task_event = TaskEvent.find_by_id(task_event_id) + work_phase = WorkPhase.find_by_id(data.get("work_phase_id")) if data.get("assignee_ids") and not cls._validate_assignees( - data.get("assignee_ids"), data + data.get("assignee_ids"), work_phase.work_id ): raise UnprocessableEntityError( "Only team members can be assigned to a task" @@ -79,15 +75,14 @@ def update_task_event(cls, data: dict, task_event_id) -> TaskEvent: return task_event @classmethod - def find_task_events(cls, work_id: int, phase_id: int) -> [TaskEvent]: - """Get all task events per workid, phaseid""" + def find_task_events(cls, work_phase_id: int) -> [TaskEvent]: + """Get all task events per work_phase_id""" return ( db.session.query(TaskEvent) .filter( TaskEvent.is_active.is_(True), TaskEvent.is_deleted.is_(False), - TaskEvent.work_id == work_id, - TaskEvent.phase_id == phase_id, + TaskEvent.work_phase_id == work_phase_id, ) .options( lazyload(TaskEvent.assignees).joinedload(TaskEventAssignee.assignee) @@ -117,9 +112,7 @@ def create_task_events_from_template( cls, params: dict, template_id: int ) -> [TaskEvent]: """Create a list of task events from the given task template""" - work_phase = WorkPhaseService.find_by_work_nd_phase( - params.get("work_id"), params.get("phase_id") - ) + work_phase = WorkPhase.find_by_id(params.get("work_phase_id")) if not work_phase: raise UnprocessableEntityError("No data found for the given work and phase") if work_phase.task_added: @@ -136,9 +129,7 @@ def create_task_events_from_template( for task in tasks: task_event_dic = { "name": task.name, - "work_id": params.get("work_id"), - "phase_id": params.get("phase_id"), - # "responsibility_id": task.responsibility_id, + "work_phase_id": params.get("work_phase_id"), "start_date": work_phase.start_date + timedelta(days=task.start_at + task.number_of_days), "number_of_days": task.number_of_days, "tips": task.tips, @@ -206,13 +197,13 @@ def _handle_assignees(cls, assignees: list, task_event_ids: List[int]) -> None: ) @classmethod - def _validate_assignees(cls, assignees: list, data: dict) -> bool: + def _validate_assignees(cls, assignees: list, work_id: int) -> bool: """Database validation""" work_staff = [ r for (r,) in db.session.query(StaffWorkRole.staff_id) .filter( - StaffWorkRole.work_id == data["work_id"], + StaffWorkRole.work_id == work_id, StaffWorkRole.is_deleted.is_(False), StaffWorkRole.is_active.is_(True), ) @@ -221,15 +212,14 @@ def _validate_assignees(cls, assignees: list, data: dict) -> bool: return all(assigne in work_staff for assigne in assignees) @classmethod - def _get_task_count(cls, work_id: int, phase_id: int): + def _get_task_count(cls, work_phase_id: int): """Task should be inserted only once.""" return ( db.session.query(TaskEvent.id) .filter( TaskEvent.is_active.is_(True), TaskEvent.is_deleted.is_(False), - TaskEvent.work_id == work_id, - TaskEvent.phase_id == phase_id, + TaskEvent.work_phase_id == work_phase_id ) .count() ) diff --git a/epictrack-web/src/components/workPlan/event/EventList.tsx b/epictrack-web/src/components/workPlan/event/EventList.tsx index c61aa9afe..a606b8e8c 100644 --- a/epictrack-web/src/components/workPlan/event/EventList.tsx +++ b/epictrack-web/src/components/workPlan/event/EventList.tsx @@ -159,13 +159,12 @@ const EventList = () => { ); } setRowSelection({}); - }, [ctx.work, ctx.selectedWorkPhase?.phase.id]); + }, [ctx.work, ctx.selectedWorkPhase?.id]); const getTaskEvents = async (): Promise => { let result: EventsGridModel[] = []; try { - const taskResult = await taskEventService.getAllByWorkNdPhase( - Number(ctx.work?.id), - Number(ctx.selectedWorkPhase?.phase.id) + const taskResult = await taskEventService.getAll( + Number(ctx.selectedWorkPhase?.id) ); if (taskResult.status === 200) { result = (taskResult.data as EventsGridModel[]).map((element) => { @@ -223,7 +222,6 @@ const EventList = () => { getCombinedEvents(); getTemplateUploadStatus(); getWorkPhases(); - // setEventId(undefined); setTaskEvent(undefined); setMilestoneEvent(undefined); }, [ctx.work, ctx.selectedWorkPhase]); @@ -238,8 +236,7 @@ const EventList = () => { try { const result = await taskEventService.importTasksFromTemplate( { - work_id: ctx.work?.id, - phase_id: ctx.selectedWorkPhase?.phase.id, + work_phase_id: ctx.selectedWorkPhase?.id, }, Number(selectedTemplateId) ); @@ -248,7 +245,7 @@ const EventList = () => { type: "success", }); setShowTemplateConfirmation(false); - getCombinedEvents(); + await getCombinedEvents(); setSelectedTemplateId(undefined); getTemplateUploadStatus(); } @@ -712,7 +709,7 @@ const EventList = () => { } setShowDeleteDialog(false); }; - + console.log("EVENTS", events); const deleteAction = ( <> {showDeleteMilestoneButton && ( diff --git a/epictrack-web/src/components/workPlan/task/TaskForm.tsx b/epictrack-web/src/components/workPlan/task/TaskForm.tsx index a71d38715..ed683ba15 100644 --- a/epictrack-web/src/components/workPlan/task/TaskForm.tsx +++ b/epictrack-web/src/components/workPlan/task/TaskForm.tsx @@ -100,8 +100,7 @@ const TaskForm = ({ onSave, taskEvent }: TaskFormProps) => { const statuses = React.useMemo(() => statusOptions, []); const onSubmitHandler = async (data: TaskEvent) => { try { - data.work_id = Number(ctx.work?.id); - data.phase_id = Number(ctx.selectedWorkPhase?.phase.id); + data.work_phase_id = Number(ctx.selectedWorkPhase?.id); data.start_date = Moment(data.start_date).format(); data.number_of_days = data.number_of_days.toString() === "" ? 0 : data.number_of_days; diff --git a/epictrack-web/src/models/project.ts b/epictrack-web/src/models/project.ts index 81bbbf806..7b02882ba 100644 --- a/epictrack-web/src/models/project.ts +++ b/epictrack-web/src/models/project.ts @@ -1,5 +1,3 @@ -import { Proponent } from "./proponent"; -import { ListType } from "./code"; import { Type } from "./type"; import { SubType } from "./subtype"; import { Region } from "./region"; diff --git a/epictrack-web/src/models/taskEvent.ts b/epictrack-web/src/models/taskEvent.ts index 98a8e68d3..6d12fd5eb 100644 --- a/epictrack-web/src/models/taskEvent.ts +++ b/epictrack-web/src/models/taskEvent.ts @@ -20,8 +20,7 @@ export const statusOptions = [ export interface TaskEvent { id: number; name: string; - work_id: number; - phase_id: number; + work_phase_id: number; start_date: string; number_of_days: number; tips: string; diff --git a/epictrack-web/src/services/taskEventService/taskEventService.ts b/epictrack-web/src/services/taskEventService/taskEventService.ts index a59a262cb..94d8e020e 100644 --- a/epictrack-web/src/services/taskEventService/taskEventService.ts +++ b/epictrack-web/src/services/taskEventService/taskEventService.ts @@ -16,9 +16,9 @@ class TaskEventService { ); } - async getAllByWorkNdPhase(workId: number, phaseId: number) { + async getAll(workPhaseId: number) { return await http.GetRequest( - `${Endpoints.TaskEvents.EVENTS}?work_id=${workId}&phase_id=${phaseId}` + `${Endpoints.TaskEvents.EVENTS}?work_phase_id=${workPhaseId}` ); } From 2933396a3e65e5abb40c95e67c6ebb76ba0a74f4 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Wed, 18 Oct 2023 13:33:11 -0700 Subject: [PATCH 02/10] nation modal changes --- ...c389ae56_added_notes_and_pip_fields_to_.py | 46 +++++++++++++ .../d8b405f9d663_create_pip_org_type_table.py | 54 +++++++++++++++ epictrack-api/src/api/models/__init__.py | 1 + .../src/api/models/indigenous_nation.py | 6 ++ epictrack-api/src/api/models/pip_org_type.py | 26 ++++++++ .../request/indigenous_nation_request.py | 18 +++++ .../indigenousNation/IndigneousNationForm.tsx | 65 ++++++++++++++++++- epictrack-web/src/models/firstNation.ts | 4 ++ epictrack-web/src/models/pipOrgType.ts | 4 ++ .../src/services/codeService/index.ts | 3 +- 10 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 epictrack-api/migrations/versions/7deec389ae56_added_notes_and_pip_fields_to_.py create mode 100644 epictrack-api/migrations/versions/d8b405f9d663_create_pip_org_type_table.py create mode 100644 epictrack-api/src/api/models/pip_org_type.py create mode 100644 epictrack-web/src/models/pipOrgType.ts diff --git a/epictrack-api/migrations/versions/7deec389ae56_added_notes_and_pip_fields_to_.py b/epictrack-api/migrations/versions/7deec389ae56_added_notes_and_pip_fields_to_.py new file mode 100644 index 000000000..081ba436a --- /dev/null +++ b/epictrack-api/migrations/versions/7deec389ae56_added_notes_and_pip_fields_to_.py @@ -0,0 +1,46 @@ +"""added notes and pip fields to indigenous nations + +Revision ID: 7deec389ae56 +Revises: d8b405f9d663 +Create Date: 2023-10-18 11:46:46.577009 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '7deec389ae56' +down_revision = 'd8b405f9d663' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('indigenous_nations', schema=None) as batch_op: + batch_op.add_column(sa.Column('notes', sa.String(), nullable=True)) + batch_op.add_column(sa.Column('pip_org_type_id', sa.Integer(), nullable=True)) + batch_op.create_foreign_key(None, 'pip_org_types', ['pip_org_type_id'], ['id']) + + with op.batch_alter_table('indigenous_nations_history', schema=None) as batch_op: + batch_op.add_column(sa.Column('notes', sa.String(), autoincrement=False, nullable=True)) + batch_op.add_column(sa.Column('pip_org_type_id', sa.Integer(), autoincrement=False, nullable=True)) + batch_op.create_foreign_key(None, 'pip_org_types', ['pip_org_type_id'], ['id']) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('indigenous_nations_history', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.drop_column('pip_org_type_id') + batch_op.drop_column('notes') + + with op.batch_alter_table('indigenous_nations', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.drop_column('pip_org_type_id') + batch_op.drop_column('notes') + + # ### end Alembic commands ### diff --git a/epictrack-api/migrations/versions/d8b405f9d663_create_pip_org_type_table.py b/epictrack-api/migrations/versions/d8b405f9d663_create_pip_org_type_table.py new file mode 100644 index 000000000..f326df029 --- /dev/null +++ b/epictrack-api/migrations/versions/d8b405f9d663_create_pip_org_type_table.py @@ -0,0 +1,54 @@ +"""create pip_org_type table + +Revision ID: d8b405f9d663 +Revises: 47b41964f6df +Create Date: 2023-10-18 11:45:36.531064 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'd8b405f9d663' +down_revision = '47b41964f6df' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('pip_org_types', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('created_by', sa.String(length=255), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', CURRENT_TIMESTAMP)"), nullable=True), + sa.Column('updated_by', sa.String(length=255), nullable=True), + sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True), + sa.Column('is_active', sa.Boolean(), server_default='t', nullable=False), + sa.Column('is_deleted', sa.Boolean(), server_default='f', nullable=False), + sa.PrimaryKeyConstraint('id'), + sqlite_autoincrement=True + ) + op.create_table('pip_org_types_history', + sa.Column('id', sa.Integer(), autoincrement=False, nullable=False), + sa.Column('name', sa.String(), autoincrement=False, nullable=False), + sa.Column('created_by', sa.String(length=255), autoincrement=False, nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), autoincrement=False, nullable=True), + sa.Column('updated_by', sa.String(length=255), autoincrement=False, nullable=True), + sa.Column('updated_at', sa.DateTime(timezone=True), autoincrement=False, nullable=True), + sa.Column('is_active', sa.Boolean(), autoincrement=False, nullable=False), + sa.Column('is_deleted', sa.Boolean(), autoincrement=False, nullable=False), + sa.Column('pk', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('during', postgresql.TSTZRANGE(), nullable=True), + sa.PrimaryKeyConstraint('id', 'pk'), + sqlite_autoincrement=True + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('pip_org_types_history') + op.drop_table('pip_org_types') + # ### end Alembic commands ### diff --git a/epictrack-api/src/api/models/__init__.py b/epictrack-api/src/api/models/__init__.py index fecad6836..0f418f165 100644 --- a/epictrack-api/src/api/models/__init__.py +++ b/epictrack-api/src/api/models/__init__.py @@ -69,3 +69,4 @@ from .action_configuration import ActionCofiguration from .task_event_responsibility import TaskEventResponsibility from .act_section import ActSection +from .pip_org_type import PIPOrgType diff --git a/epictrack-api/src/api/models/indigenous_nation.py b/epictrack-api/src/api/models/indigenous_nation.py index 533d6e639..c93f0ed0a 100644 --- a/epictrack-api/src/api/models/indigenous_nation.py +++ b/epictrack-api/src/api/models/indigenous_nation.py @@ -30,6 +30,7 @@ class IndigenousNation(db.Model, CodeTableVersioned): name = Column(String(255), nullable=False) is_active = Column(Boolean, default=True, nullable=False) pip_link = Column(URLType, default=None, nullable=True) + notes = Column(String) relationship_holder_id = Column( ForeignKey("staffs.id"), nullable=True, default=None @@ -38,6 +39,11 @@ class IndigenousNation(db.Model, CodeTableVersioned): "Staff", foreign_keys=[relationship_holder_id], lazy="select" ) + pip_org_type_id = Column(ForeignKey("pip_org_types.id")) + pip_org_type = relationship( + "PIPOrgType", foreign_keys=[pip_org_type_id], lazy="select" + ) + def as_dict(self): """Return JSON Representation.""" result = CodeTableVersioned.as_dict(self) diff --git a/epictrack-api/src/api/models/pip_org_type.py b/epictrack-api/src/api/models/pip_org_type.py new file mode 100644 index 000000000..8b3ff3c5d --- /dev/null +++ b/epictrack-api/src/api/models/pip_org_type.py @@ -0,0 +1,26 @@ +# Copyright © 2019 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Model to handle all operations related to Region.""" + +from sqlalchemy import Column, Integer, String +from .code_table import CodeTableVersioned +from .db import db + +class PIPOrgType(db.Model, CodeTableVersioned): + """Model class for PIP Organization Types.""" + + __tablename__ = 'pip_org_types' + + id = Column(Integer, primary_key=True, autoincrement=True) + name = Column(String(), nullable=False) diff --git a/epictrack-api/src/api/schemas/request/indigenous_nation_request.py b/epictrack-api/src/api/schemas/request/indigenous_nation_request.py index f7771d0ca..76f028b9c 100644 --- a/epictrack-api/src/api/schemas/request/indigenous_nation_request.py +++ b/epictrack-api/src/api/schemas/request/indigenous_nation_request.py @@ -37,6 +37,24 @@ class IndigenousNationBodyParameterSchema(RequestBodyParameterSchema): is_active = fields.Bool( metadata={"description": "Active state of the indigenous nation"}) + + notes = fields.Str( + metadata={"description": "Notes for the indigenous nation"}, + allow_none=True + ) + + pip_org_type_id = fields.Int( + metadata={"description": "PIP organization type for the indigenous nation"}, + validate=validate.Range(min=1), + allow_none=True, + missing=None + ) + + pip_link = fields.Str( + metadata={"description": "PIP site URL for indigenous nation"}, + allow_none=True, + missing=None + ) class IndigenousNationExistenceQueryParamSchema(RequestQueryParameterSchema): diff --git a/epictrack-web/src/components/indigenousNation/IndigneousNationForm.tsx b/epictrack-web/src/components/indigenousNation/IndigneousNationForm.tsx index d7c286578..82f6675c4 100644 --- a/epictrack-web/src/components/indigenousNation/IndigneousNationForm.tsx +++ b/epictrack-web/src/components/indigenousNation/IndigneousNationForm.tsx @@ -11,6 +11,10 @@ import { FirstNation } from "../../models/firstNation"; import staffService from "../../services/staffService/staffService"; import ControlledSelectV2 from "../shared/controlledInputComponents/ControlledSelectV2"; import { MasterContext } from "../shared/MasterContext"; +import { PIPOrgType } from "../../models/pipOrgType"; +import ControlledSwitch from "../shared/controlledInputComponents/ControlledSwitch"; +import RichTextEditor from "../shared/richTextEditor"; +import codeService, { Code } from "../../services/codeService"; const schema = yup.object().shape({ name: yup @@ -31,16 +35,23 @@ const schema = yup.object().shape({ return true; } ), + pip_url: yup.string(), }); export default function IndigenousNationForm({ ...props }) { const [staffs, setStaffs] = React.useState([]); + const [pipOrgTypes, setPipOrgTypes] = React.useState([]); + const [notes, setNotes] = React.useState(""); const ctx = React.useContext(MasterContext); React.useEffect(() => { ctx.setFormId("indigenous-nation-form"); }, []); + React.useEffect(() => { + ctx.setTitle(ctx.item ? (ctx.item as FirstNation)?.name : "Nation"); + }, [ctx.title, ctx.item]); + React.useEffect(() => { ctx.setId(props.indigenousNationID); }, [ctx.id]); @@ -60,6 +71,9 @@ export default function IndigenousNationForm({ ...props }) { React.useEffect(() => { reset(ctx.item); + if (ctx.item) { + setNotes((ctx.item as FirstNation)?.notes || ""); + } }, [ctx.item]); const getStaffs = async () => { @@ -68,14 +82,37 @@ export default function IndigenousNationForm({ ...props }) { setStaffs(staffsResult.data as never); } }; + + const codeTypes: { [x: string]: any } = { + pip_org_types: setPipOrgTypes, + }; + + const getCodes = async (code: Code) => { + const codeResult = await codeService.getCodes(code); + if (codeResult.status === 200) { + codeTypes[code]((codeResult.data as never)["codes"]); + } + }; + React.useEffect(() => { getStaffs(); + const promises: any[] = []; + Object.keys(codeTypes).forEach(async (key) => { + promises.push(getCodes(key as Code)); + }); + Promise.all(promises); }, []); + const onSubmitHandler = async (data: FirstNation) => { + data.notes = notes; ctx.onSave(data, () => { reset(); }); + ctx.setId(undefined); }; + + console.log(pipOrgTypes); + return ( <> @@ -87,8 +124,9 @@ export default function IndigenousNationForm({ ...props }) { onSubmit={handleSubmit(onSubmitHandler)} > - Name + Name Relationship Holder + + PIP Organization Type + (o ? o.name : "")} + getOptionValue={(o: PIPOrgType) => (o ? o.id.toString() : "")} + options={pipOrgTypes || []} + {...register("pip_org_type_id")} + > + + + PIP URL + + + + Notes + + - diff --git a/epictrack-web/src/models/firstNation.ts b/epictrack-web/src/models/firstNation.ts index fb9f20d6a..05e90ddff 100644 --- a/epictrack-web/src/models/firstNation.ts +++ b/epictrack-web/src/models/firstNation.ts @@ -1,12 +1,16 @@ import { ListType } from "./code"; import { Staff } from "./staff"; import { MasterBase } from "./type"; +import { PIPOrgType } from "./pipOrgType"; export interface FirstNation extends ListType, MasterBase { is_active: boolean; relationship_holder_id?: number; relationship_holder?: Staff; pip_link: string; + notes?: string; + pip_org_type_id: number; + pip_org_type: PIPOrgType; } export interface WorkFirstNation extends ListType, MasterBase { diff --git a/epictrack-web/src/models/pipOrgType.ts b/epictrack-web/src/models/pipOrgType.ts new file mode 100644 index 000000000..22efa05d5 --- /dev/null +++ b/epictrack-web/src/models/pipOrgType.ts @@ -0,0 +1,4 @@ +import { ListType } from "./code"; +import { MasterBase } from "./type"; + +export interface PIPOrgType extends ListType, MasterBase {} diff --git a/epictrack-web/src/services/codeService/index.ts b/epictrack-web/src/services/codeService/index.ts index 75aa34c65..ff68017c0 100644 --- a/epictrack-web/src/services/codeService/index.ts +++ b/epictrack-web/src/services/codeService/index.ts @@ -16,7 +16,8 @@ export type Code = | "federal_involvements" | "responsibilities" | "roles" - | "substitution_acts"; + | "substitution_acts" + | "pip_org_types"; const getCodes = async (codeType: Code, apiUrl?: string) => { return await http.GetRequest(Endpoints.Codes.GET_CODES + `/${codeType}`); From 3268b44789f83023ef313787114e24e4e0cb00f6 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Mon, 23 Oct 2023 10:17:53 -0700 Subject: [PATCH 03/10] trying to fix linting error --- .../src/components/indigenousNation/IndigneousNationForm.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/epictrack-web/src/components/indigenousNation/IndigneousNationForm.tsx b/epictrack-web/src/components/indigenousNation/IndigneousNationForm.tsx index 82f6675c4..69c14b6b3 100644 --- a/epictrack-web/src/components/indigenousNation/IndigneousNationForm.tsx +++ b/epictrack-web/src/components/indigenousNation/IndigneousNationForm.tsx @@ -1,11 +1,10 @@ import React from "react"; -import { TextField, Grid, Button } from "@mui/material"; +import { TextField, Grid } from "@mui/material"; import { FormProvider, useForm } from "react-hook-form"; import * as yup from "yup"; import { yupResolver } from "@hookform/resolvers/yup"; import { ETFormLabel } from "../shared/index"; import { Staff } from "../../models/staff"; -import ControlledCheckbox from "../shared/controlledInputComponents/ControlledCheckbox"; import indigenousNationService from "../../services/indigenousNationService/indigenousNationService"; import { FirstNation } from "../../models/firstNation"; import staffService from "../../services/staffService/staffService"; From 79c5cd9a3b29bdef6d80bbe9ea9937f5843608b8 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Mon, 23 Oct 2023 10:41:31 -0700 Subject: [PATCH 04/10] trying to fix linting error --- epictrack-api/src/api/models/pip_org_type.py | 1 + 1 file changed, 1 insertion(+) diff --git a/epictrack-api/src/api/models/pip_org_type.py b/epictrack-api/src/api/models/pip_org_type.py index 8b3ff3c5d..88218acee 100644 --- a/epictrack-api/src/api/models/pip_org_type.py +++ b/epictrack-api/src/api/models/pip_org_type.py @@ -17,6 +17,7 @@ from .code_table import CodeTableVersioned from .db import db + class PIPOrgType(db.Model, CodeTableVersioned): """Model class for PIP Organization Types.""" From 967f79814871238e96d45cd2f589568a8cac2ec1 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Mon, 23 Oct 2023 10:50:23 -0700 Subject: [PATCH 05/10] trying to fix linting error --- epictrack-api/src/api/models/pip_org_type.py | 1 + 1 file changed, 1 insertion(+) diff --git a/epictrack-api/src/api/models/pip_org_type.py b/epictrack-api/src/api/models/pip_org_type.py index 88218acee..9822e3839 100644 --- a/epictrack-api/src/api/models/pip_org_type.py +++ b/epictrack-api/src/api/models/pip_org_type.py @@ -14,6 +14,7 @@ """Model to handle all operations related to Region.""" from sqlalchemy import Column, Integer, String + from .code_table import CodeTableVersioned from .db import db From 2e7bbc5c69268b2dd1dc8b1bdf462385ea848122 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Mon, 23 Oct 2023 10:57:31 -0700 Subject: [PATCH 06/10] trying to fix linting error --- ...py => 7deec389ae56_added_notes_and_pip_fields_to_nations.py} | 2 +- epictrack-api/src/api/models/pip_org_type.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename epictrack-api/migrations/versions/{7deec389ae56_added_notes_and_pip_fields_to_.py => 7deec389ae56_added_notes_and_pip_fields_to_nations.py} (96%) diff --git a/epictrack-api/migrations/versions/7deec389ae56_added_notes_and_pip_fields_to_.py b/epictrack-api/migrations/versions/7deec389ae56_added_notes_and_pip_fields_to_nations.py similarity index 96% rename from epictrack-api/migrations/versions/7deec389ae56_added_notes_and_pip_fields_to_.py rename to epictrack-api/migrations/versions/7deec389ae56_added_notes_and_pip_fields_to_nations.py index 081ba436a..f6526d0ab 100644 --- a/epictrack-api/migrations/versions/7deec389ae56_added_notes_and_pip_fields_to_.py +++ b/epictrack-api/migrations/versions/7deec389ae56_added_notes_and_pip_fields_to_nations.py @@ -1,4 +1,4 @@ -"""added notes and pip fields to indigenous nations +"""added notes and pip fields to indigenous nations. Revision ID: 7deec389ae56 Revises: d8b405f9d663 diff --git a/epictrack-api/src/api/models/pip_org_type.py b/epictrack-api/src/api/models/pip_org_type.py index 9822e3839..fab820f53 100644 --- a/epictrack-api/src/api/models/pip_org_type.py +++ b/epictrack-api/src/api/models/pip_org_type.py @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Model to handle all operations related to Region.""" +"""Model to handle all operations related to PIP Organization Types.""" from sqlalchemy import Column, Integer, String From 20c59360241e690b0cdb1ed01fd5a244173422fe Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Mon, 23 Oct 2023 11:26:49 -0700 Subject: [PATCH 07/10] trying to fix linting error --- epictrack-web/src/App.tsx | 3 --- epictrack-web/src/components/icons/checkbox.tsx | 1 - epictrack-web/src/components/icons/index.tsx | 2 +- .../src/components/indigenousNation/IndigenousNationList.tsx | 4 ++-- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/epictrack-web/src/App.tsx b/epictrack-web/src/App.tsx index 553c6d20c..5c473d39b 100644 --- a/epictrack-web/src/App.tsx +++ b/epictrack-web/src/App.tsx @@ -14,9 +14,6 @@ export function App() { const isLoggedIn = useAppSelector( (state) => state.user?.authentication.authenticated ); - const isMediumScreen: boolean = useMediaQuery((theme: Theme) => - theme.breakpoints.up("md") - ); const uiState = useAppSelector((state) => state.uiState); const drawerWidth = uiState.drawerWidth; React.useEffect(() => { diff --git a/epictrack-web/src/components/icons/checkbox.tsx b/epictrack-web/src/components/icons/checkbox.tsx index a3027ebd1..93bfc1199 100644 --- a/epictrack-web/src/components/icons/checkbox.tsx +++ b/epictrack-web/src/components/icons/checkbox.tsx @@ -1,4 +1,3 @@ -import React, { ReactNode } from "react"; import { IconProps } from "./type"; const commonProps = { diff --git a/epictrack-web/src/components/icons/index.tsx b/epictrack-web/src/components/icons/index.tsx index 6a32f22d7..04d900d44 100644 --- a/epictrack-web/src/components/icons/index.tsx +++ b/epictrack-web/src/components/icons/index.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from "react"; +import React from "react"; import { IconProps } from "./type"; const commonProps = { diff --git a/epictrack-web/src/components/indigenousNation/IndigenousNationList.tsx b/epictrack-web/src/components/indigenousNation/IndigenousNationList.tsx index 5c75baa05..5b7c0cc1e 100644 --- a/epictrack-web/src/components/indigenousNation/IndigenousNationList.tsx +++ b/epictrack-web/src/components/indigenousNation/IndigenousNationList.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { Edit as EditIcon, Delete as DeleteIcon } from "@mui/icons-material"; -import { Box, Button, IconButton, Grid, Chip } from "@mui/material"; +import { Delete as DeleteIcon } from "@mui/icons-material"; +import { Box, Button, IconButton, Grid } from "@mui/material"; import { MRT_ColumnDef } from "material-react-table"; import indigenousNationService from "../../services/indigenousNationService/indigenousNationService"; import { FirstNation } from "../../models/firstNation"; From 721cb845431a0306592ec9e025e14bebcce09ff2 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Mon, 23 Oct 2023 11:32:35 -0700 Subject: [PATCH 08/10] trying to fix linting error --- epictrack-web/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epictrack-web/src/App.tsx b/epictrack-web/src/App.tsx index 5c473d39b..375328c70 100644 --- a/epictrack-web/src/App.tsx +++ b/epictrack-web/src/App.tsx @@ -4,7 +4,7 @@ import Header from "./components/layout/Header/Header"; import UserService from "./services/userService"; import AuthenticatedRoutes from "./routes/AuthenticatedRoutes"; import { useAppDispatch, useAppSelector } from "./hooks"; -import { Box, Theme, useMediaQuery } from "@mui/material"; +import { Box } from "@mui/material"; import AxiosErrorHandler from "./components/axiosErrorHandler/AxiosErrorHandler"; import ETNotificationProvider from "./components/shared/notificationProvider/ETNotificationProvider"; import "./styles/App.scss"; From 5beda4d88d93a5f0563fe5eac51af23dda1d426f Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Mon, 23 Oct 2023 13:21:31 -0700 Subject: [PATCH 09/10] trying to fix linting error --- .../src/api/schemas/request/indigenous_nation_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epictrack-api/src/api/schemas/request/indigenous_nation_request.py b/epictrack-api/src/api/schemas/request/indigenous_nation_request.py index 76f028b9c..dc0cb40cd 100644 --- a/epictrack-api/src/api/schemas/request/indigenous_nation_request.py +++ b/epictrack-api/src/api/schemas/request/indigenous_nation_request.py @@ -37,7 +37,7 @@ class IndigenousNationBodyParameterSchema(RequestBodyParameterSchema): is_active = fields.Bool( metadata={"description": "Active state of the indigenous nation"}) - + notes = fields.Str( metadata={"description": "Notes for the indigenous nation"}, allow_none=True From aa62bb037c129d52d62b150318faf3cd99f2bdf1 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Mon, 23 Oct 2023 13:50:21 -0700 Subject: [PATCH 10/10] add data insert into migration --- .../d8b405f9d663_create_pip_org_type_table.py | 29 ++++++++++++++++++- epictrack-web/src/App.tsx | 5 +++- .../src/components/icons/checkbox.tsx | 1 + .../indigenousNation/IndigenousNationList.tsx | 4 +-- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/epictrack-api/migrations/versions/d8b405f9d663_create_pip_org_type_table.py b/epictrack-api/migrations/versions/d8b405f9d663_create_pip_org_type_table.py index f326df029..ade633783 100644 --- a/epictrack-api/migrations/versions/d8b405f9d663_create_pip_org_type_table.py +++ b/epictrack-api/migrations/versions/d8b405f9d663_create_pip_org_type_table.py @@ -18,7 +18,7 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('pip_org_types', + pip_org_type = op.create_table('pip_org_types', sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('name', sa.String(), nullable=False), sa.Column('created_by', sa.String(length=255), nullable=True), @@ -44,6 +44,33 @@ def upgrade(): sa.PrimaryKeyConstraint('id', 'pk'), sqlite_autoincrement=True ) + op.bulk_insert(pip_org_type, + [ + { + "name": "Clan" + }, + { + "name": "First Nation" + }, + { + "name": "First Nation Group" + }, + { + "name": "House" + }, + { + "name": "Keyoh" + }, + { + "name": "Metis" + }, + { + "name": "Tribal Council" + }, + { + "name": "Wilp" + } + ]) # ### end Alembic commands ### diff --git a/epictrack-web/src/App.tsx b/epictrack-web/src/App.tsx index 375328c70..553c6d20c 100644 --- a/epictrack-web/src/App.tsx +++ b/epictrack-web/src/App.tsx @@ -4,7 +4,7 @@ import Header from "./components/layout/Header/Header"; import UserService from "./services/userService"; import AuthenticatedRoutes from "./routes/AuthenticatedRoutes"; import { useAppDispatch, useAppSelector } from "./hooks"; -import { Box } from "@mui/material"; +import { Box, Theme, useMediaQuery } from "@mui/material"; import AxiosErrorHandler from "./components/axiosErrorHandler/AxiosErrorHandler"; import ETNotificationProvider from "./components/shared/notificationProvider/ETNotificationProvider"; import "./styles/App.scss"; @@ -14,6 +14,9 @@ export function App() { const isLoggedIn = useAppSelector( (state) => state.user?.authentication.authenticated ); + const isMediumScreen: boolean = useMediaQuery((theme: Theme) => + theme.breakpoints.up("md") + ); const uiState = useAppSelector((state) => state.uiState); const drawerWidth = uiState.drawerWidth; React.useEffect(() => { diff --git a/epictrack-web/src/components/icons/checkbox.tsx b/epictrack-web/src/components/icons/checkbox.tsx index 93bfc1199..a3027ebd1 100644 --- a/epictrack-web/src/components/icons/checkbox.tsx +++ b/epictrack-web/src/components/icons/checkbox.tsx @@ -1,3 +1,4 @@ +import React, { ReactNode } from "react"; import { IconProps } from "./type"; const commonProps = { diff --git a/epictrack-web/src/components/indigenousNation/IndigenousNationList.tsx b/epictrack-web/src/components/indigenousNation/IndigenousNationList.tsx index 5b7c0cc1e..5c75baa05 100644 --- a/epictrack-web/src/components/indigenousNation/IndigenousNationList.tsx +++ b/epictrack-web/src/components/indigenousNation/IndigenousNationList.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { Delete as DeleteIcon } from "@mui/icons-material"; -import { Box, Button, IconButton, Grid } from "@mui/material"; +import { Edit as EditIcon, Delete as DeleteIcon } from "@mui/icons-material"; +import { Box, Button, IconButton, Grid, Chip } from "@mui/material"; import { MRT_ColumnDef } from "material-react-table"; import indigenousNationService from "../../services/indigenousNationService/indigenousNationService"; import { FirstNation } from "../../models/firstNation";