Skip to content

Commit

Permalink
feat (frontend,backend): my organisations tab implemented (#1227)
Browse files Browse the repository at this point in the history
* feat: my organisations api route added

* feat: get my organisation function

* feat: added get organisation api service

* feat: organisationGridCard Component created

* feat: Type added to organisation slice

* Feat: My organisation tab for data fetching

* refactor: remove missed print statement

---------

Co-authored-by: spwoodcock <sam.woodcock@protonmail.com>
  • Loading branch information
varun2948 and spwoodcock authored Feb 20, 2024
1 parent 873517c commit 9a8867d
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 72 deletions.
19 changes: 19 additions & 0 deletions src/backend/app/organisations/organisation_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,25 @@ async def get_organisations(
return db.query(db_models.DbOrganisation).filter_by(approved=True).all()


async def get_my_organisations(
db: Session,
current_user: AuthUser,
) -> list[db_models.DbOrganisation]:
"""Get organisations filtered by the current user.
Args:
db (Session): The database session.
current_user (AuthUser): The current user.
Returns:
list[db_models.DbOrganisation]: A list of organisations
filtered by the current user.
"""
db_user = await get_user(db, current_user.id)

return db_user.organisations


async def get_unapproved_organisations(
db: Session,
) -> list[db_models.DbOrganisation]:
Expand Down
11 changes: 11 additions & 0 deletions src/backend/app/organisations/organisation_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ async def get_organisations(
return await organisation_crud.get_organisations(db, current_user)


@router.get(
"/my-organisations", response_model=list[organisation_schemas.OrganisationOut]
)
async def get_my_organisations(
db: Session = Depends(database.get_db),
current_user: AuthUser = Depends(login_required),
) -> list[DbOrganisation]:
"""Get a list of all organisations."""
return await organisation_crud.get_my_organisations(db, current_user)


@router.get("/unapproved/", response_model=list[organisation_schemas.OrganisationOut])
async def list_unapproved_organisations(
db: Session = Depends(database.get_db),
Expand Down
16 changes: 16 additions & 0 deletions src/frontend/src/api/OrganisationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@ export const OrganisationDataService: Function = (url: string) => {
};
};

export const MyOrganisationDataService: Function = (url: string) => {
return async (dispatch) => {
dispatch(OrganisationAction.GetMyOrganisationDataLoading(true));
const getMyOrganisationData = async (url) => {
try {
const getMyOrganisationDataResponse = await API.get(url);
const response: GetOrganisationDataModel[] = getMyOrganisationDataResponse.data;
dispatch(OrganisationAction.GetMyOrganisationsData(response));
} catch (error) {
dispatch(OrganisationAction.GetMyOrganisationDataLoading(false));
}
};
await getMyOrganisationData(url);
};
};

export const PostOrganisationDataService: Function = (url: string, payload: any) => {
return async (dispatch) => {
dispatch(OrganisationAction.SetOrganisationFormData({}));
Expand Down
53 changes: 53 additions & 0 deletions src/frontend/src/components/organisation/OrganisationGridCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import CoreModules from '@/shared/CoreModules';
import CustomizedImage from '@/utilities/CustomizedImage';

const OrganisationGridCard = ({ filteredData, allDataLength }) => {
const cardStyle = {
padding: '20px',
display: 'flex',
flexDirection: 'row',
cursor: 'pointer',
gap: '20px',
boxShadow: 'none',
borderRadius: '0px',
};
return (
<div>
<p className="fmtm-text-[#9B9999]">
Showing {filteredData?.length} of {allDataLength} organizations
</p>
<div className="fmtm-grid fmtm-grid-cols-1 md:fmtm-grid-cols-2 lg:fmtm-grid-cols-3 fmtm-gap-5">
{filteredData?.map((data, index) => (
<CoreModules.Card key={index} sx={cardStyle}>
{data.logo ? (
<div className="fmtm-min-w-[60px] md:fmtm-min-w-[80px] lg:fmtm-min-w-[120px]">
<CoreModules.CardMedia component="img" src={data.logo} sx={{ width: ['60px', '80px', '120px'] }} />
</div>
) : (
<div className="fmtm-min-w-[60px] fmtm-max-w-[60px] md:fmtm-min-w-[80px] md:fmtm-max-w-[80px] lg:fmtm-min-w-[120px] lg:fmtm-max-w-[120px]">
<CustomizedImage status={'card'} style={{ width: '100%' }} />
</div>
)}

<CoreModules.Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }} className="fmtm-overflow-hidden">
<h2
className="fmtm-line-clamp-1 fmtm-text-base sm:fmtm-text-lg fmtm-font-bold fmtm-capitalize"
title={data.name}
>
{data.name}
</h2>
<p
className="fmtm-line-clamp-3 fmtm-text-[#7A7676] fmtm-font-archivo fmtm-text-sm sm:fmtm-text-base"
title={data.description}
>
{data.description}
</p>
</CoreModules.Box>
</CoreModules.Card>
))}
</div>
</div>
);
};
export default OrganisationGridCard;
50 changes: 30 additions & 20 deletions src/frontend/src/store/slices/organisationSlice.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,43 @@
import CoreModules from '@/shared/CoreModules.js';
import { IOrganisationState } from '../types/IOrganisation';

const initialState: IOrganisationState = {
organisationFormData: {},
organisationData: [],
myOrganisationData: [],
postOrganisationData: null,
organisationDataLoading: false,
myOrganisationDataLoading: false,
postOrganisationDataLoading: false,
consentDetailsFormData: {
give_consent: '',
review_documentation: [],
log_into: [],
participated_in: [],
},
consentApproval: false,
organizationApprovalStatus: {
isSuccess: false,
organizationApproving: false,
organizationRejecting: false,
},
};
const OrganisationSlice = CoreModules.createSlice({
name: 'organisation',
initialState: {
organisationFormData: {},
organisationData: [],
postOrganisationData: null,
organisationDataLoading: false,
postOrganisationDataLoading: false,
consentDetailsFormData: {
give_consent: '',
review_documentation: [],
log_into: [],
participated_in: [],
},
consentApproval: false,
organizationApprovalStatus: {
isSuccess: false,
organizationApproving: false,
organizationRejecting: false,
},
},
initialState: initialState,
reducers: {
GetOrganisationsData(state, action) {
state.oraganizationData = action.payload;
state.organisationData = action.payload;
},
GetOrganisationDataLoading(state, action) {
state.organisationDataLoading = action.payload;
},
GetMyOrganisationsData(state, action) {
state.myOrganisationData = action.payload;
},
GetMyOrganisationDataLoading(state, action) {
state.myOrganisationDataLoading = action.payload;
},
postOrganisationData(state, action) {
state.postOrganisationData = action.payload;
},
Expand Down
23 changes: 23 additions & 0 deletions src/frontend/src/store/types/IOrganisation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { GetOrganisationDataModel } from '@/models/organisation/organisationModel';

export interface IOrganisationState {
organisationFormData: any;
organisationData: GetOrganisationDataModel[];
myOrganisationData: GetOrganisationDataModel[];
postOrganisationData: any;
organisationDataLoading: Boolean;
postOrganisationDataLoading: Boolean;
myOrganisationDataLoading: false;
consentDetailsFormData: {
give_consent: any;
review_documentation: any;
log_into: any;
participated_in: any;
};
consentApproval: Boolean;
organizationApprovalStatus: {
isSuccess: Boolean;
organizationApproving: Boolean;
organizationRejecting: Boolean;
};
}
79 changes: 27 additions & 52 deletions src/frontend/src/views/Organisation.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
import React, { useEffect, useState } from 'react';
import CoreModules from '@/shared/CoreModules';
import AssetModules from '@/shared/AssetModules';
import { OrganisationDataService } from '@/api/OrganisationService';
import { MyOrganisationDataService, OrganisationDataService } from '@/api/OrganisationService';
import { user_roles } from '@/types/enums';
import CustomizedImage from '@/utilities/CustomizedImage';
import { GetOrganisationDataModel } from '@/models/organisation/organisationModel';
import OrganisationGridCard from '@/components/organisation/OrganisationGridCard';
import { useNavigate } from 'react-router-dom';

const Organisation = () => {
const cardStyle = {
padding: '20px',
display: 'flex',
flexDirection: 'row',
cursor: 'pointer',
gap: '20px',
boxShadow: 'none',
borderRadius: '0px',
};

const navigate = useNavigate();

const [searchKeyword, setSearchKeyword] = useState<string>('');
Expand All @@ -31,13 +21,21 @@ const Organisation = () => {

const dispatch = CoreModules.useAppDispatch();

const oraganizationData: GetOrganisationDataModel[] = CoreModules.useAppSelector(
(state) => state.organisation.oraganizationData,
const organisationData: GetOrganisationDataModel[] = CoreModules.useAppSelector(
(state) => state.organisation.organisationData,
);
const filteredCardData: GetOrganisationDataModel[] = oraganizationData?.filter((data) =>
data.name.toLowerCase().includes(searchKeyword.toLowerCase()),
const myOrganisationData: GetOrganisationDataModel[] = CoreModules.useAppSelector(
(state) => state.organisation.myOrganisationData,
);

const filteredBySearch = (data, searchKeyword) => {
const filteredCardData: GetOrganisationDataModel[] = data?.filter((d) =>
d.name.toLowerCase().includes(searchKeyword.toLowerCase()),
);
return filteredCardData;
};
useEffect(() => {
dispatch(MyOrganisationDataService(`${import.meta.env.VITE_API_URL}/organisation/my-organisations`));
}, []);
useEffect(() => {
if (verifiedTab) {
dispatch(OrganisationDataService(`${import.meta.env.VITE_API_URL}/organisation/`));
Expand Down Expand Up @@ -179,41 +177,18 @@ const Organisation = () => {
className="fmtm-min-w-[14rem] lg:fmtm-w-[20%]"
/>
</CoreModules.Box>
<div>
<p className="fmtm-text-[#9B9999]">
Showing {filteredCardData?.length} of {oraganizationData?.length} organizations
</p>
</div>
<CoreModules.Box className="fmtm-grid fmtm-grid-cols-1 md:fmtm-grid-cols-2 lg:fmtm-grid-cols-3 fmtm-gap-5">
{filteredCardData?.map((data, index) => (
<CoreModules.Card key={index} sx={cardStyle} onClick={() => !verifiedTab && approveOrganization(data.id)}>
{data.logo ? (
<div className="fmtm-min-w-[60px] md:fmtm-min-w-[80px] lg:fmtm-min-w-[120px]">
<CoreModules.CardMedia component="img" src={data.logo} sx={{ width: ['60px', '80px', '120px'] }} />
</div>
) : (
<div className="fmtm-min-w-[60px] fmtm-max-w-[60px] md:fmtm-min-w-[80px] md:fmtm-max-w-[80px] lg:fmtm-min-w-[120px] lg:fmtm-max-w-[120px]">
<CustomizedImage status={'card'} style={{ width: '100%' }} />
</div>
)}

<CoreModules.Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }} className="fmtm-overflow-hidden">
<h2
className="fmtm-line-clamp-1 fmtm-text-base sm:fmtm-text-lg fmtm-font-bold fmtm-capitalize"
title={data.name}
>
{data.name}
</h2>
<p
className="fmtm-line-clamp-3 fmtm-text-[#7A7676] fmtm-font-archivo fmtm-text-sm sm:fmtm-text-base"
title={data.description}
>
{data.description}
</p>
</CoreModules.Box>
</CoreModules.Card>
))}
</CoreModules.Box>
{activeTab === 0 ? (
<OrganisationGridCard
filteredData={filteredBySearch(organisationData, searchKeyword)}
allDataLength={organisationData?.length}
/>
) : null}
{activeTab === 1 ? (
<OrganisationGridCard
filteredData={filteredBySearch(myOrganisationData, searchKeyword)}
allDataLength={myOrganisationData?.length}
/>
) : null}
</CoreModules.Box>
);
};
Expand Down

0 comments on commit 9a8867d

Please sign in to comment.