Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/ My organisations implemented with api and UI #1227

Merged
merged 8 commits into from
Feb 20, 2024
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.PostOrganisationDataLoading(true));
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
Loading