Skip to content

Commit

Permalink
Merge pull request #109 from naxa-developers/feat-partner-link
Browse files Browse the repository at this point in the history
Feat partner link
  • Loading branch information
prabinoid authored Jul 25, 2024
2 parents adf51cc + 42a7805 commit 6a18977
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 63 deletions.
43 changes: 21 additions & 22 deletions backend/api/projects/partnerships.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,19 @@
from backend.models.postgis.utils import timestamp


def check_if_manager(partnership_dto: ProjectPartnershipDTO):
if not ProjectAdminService.is_user_action_permitted_on_project(
token_auth.current_user(), partnership_dto.project_id
):
return {
"Error": "User is not a manager of the project",
"SubCode": "UserPermissionError",
}, 401


class ProjectPartnershipsRestApi(Resource):
def get(self, partnership_id: int):
@staticmethod
def get(partnership_id: int):
"""
Retrieves a Partnership by id
---
Expand Down Expand Up @@ -98,13 +109,9 @@ def post(self):
"""
partnership_dto = ProjectPartnershipDTO(request.get_json())

if not ProjectAdminService.is_user_action_permitted_on_project(
token_auth.current_user(), partnership_dto.project_id
):
return {
"Error": "User is not a manager of the project",
"SubCode": "UserPermissionError",
}, 401
is_not_manager_error = check_if_manager(partnership_dto)
if is_not_manager_error is not None:
return is_not_manager_error

if partnership_dto.started_on is None:
partnership_dto.started_on = timestamp()
Expand Down Expand Up @@ -183,13 +190,9 @@ def patch(partnership_id: int):
partnership_id
)

if not ProjectAdminService.is_user_action_permitted_on_project(
token_auth.current_user(), partnership_dto.project_id
):
return {
"Error": "User is not a manager of the project",
"SubCode": "UserPermissionError",
}, 401
is_not_manager_error = check_if_manager(partnership_dto)
if is_not_manager_error is not None:
return is_not_manager_error

partnership = ProjectPartnershipService.update_partnership_time_range(
partnership_id,
Expand Down Expand Up @@ -248,13 +251,9 @@ def delete(partnership_id: int):
partnership_id
)

if not ProjectAdminService.is_user_action_permitted_on_project(
token_auth.current_user(), partnership_dto.project_id
):
return {
"Error": "User is not a manager of the project",
"SubCode": "UserPermissionError",
}, 401
is_not_manager_error = check_if_manager(partnership_dto)
if is_not_manager_error is not None:
return is_not_manager_error

ProjectPartnershipService.delete_partnership(partnership_id)
return (
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/api/projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,18 @@ export const useAvailableCountriesQuery = () => {
});
};

export const useAllPartnersQuery = (token, userId) => {
const fetchAllPartners = () => {
return api(token).get('partners/');
};

return useQuery({
queryKey: ['all-partners', userId],
queryFn: fetchAllPartners,
select: (response) => response.data,
});
};

const backendToQueryConversion = {
difficulty: 'difficulty',
campaign: 'campaign',
Expand Down
55 changes: 32 additions & 23 deletions frontend/src/components/projectEdit/partnersForm.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { useEffect, useState, forwardRef, useMemo } from 'react';
import { useParams } from 'react-router-dom';

import { useSelector } from 'react-redux';
import Select from 'react-select';
import ReactDatePicker from 'react-datepicker';
import { FormattedMessage } from 'react-intl';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { format } from 'date-fns';
import toast from 'react-hot-toast';
import PropTypes from 'prop-types';

import messages from './messages';
import { Alert } from '../alert';
import { ChevronDownIcon, CloseIcon } from '../svgIcons';
import { Button } from '../button';
import { styleClasses } from '../../views/projectEdit';
import { fetchLocalJSONAPI, pushToLocalJSONAPI } from '../../network/genericJSONRequest';
import { pushToLocalJSONAPI } from '../../network/genericJSONRequest';
import { useAllPartnersQuery } from '../../api/projects';
import { Listing } from './partnersListing';

export const DateCustomInput = forwardRef(
Expand All @@ -36,26 +37,39 @@ export const DateCustomInput = forwardRef(
}}
</FormattedMessage>

{(date && hideCloseIcon) || !date ? (
<div className="absolute right-1 pointer" style={{ top: '0.9rem' }} onClick={onClick}>
{((date && hideCloseIcon) || !date) && (
<button
className="absolute pointer b--none bg-inherit"
style={{ top: '0.75rem', right: '0.25rem' }}
onClick={onClick}
>
<ChevronDownIcon style={{ color: 'grey', width: '12px', height: '12px' }} />
</div>
) : null}
</button>
)}

{date && !hideCloseIcon ? (
<div
className="absolute right-1 pointer"
style={{ top: '0.75rem' }}
{date && !hideCloseIcon && (
<button
className="absolute pointer b--none bg-inherit"
style={{ top: '0.7rem', right: '0.25rem' }}
onClick={handleClear}
>
<CloseIcon style={{ color: 'grey', width: '10px', height: '10px' }} />
</div>
) : null}
</button>
)}
</div>
);
},
);

DateCustomInput.propTypes = {
value: PropTypes.string,
date: PropTypes.instanceOf(Date),
onClick: PropTypes.func.isRequired,
handleClear: PropTypes.func,
isStartDate: PropTypes.bool,
hideCloseIcon: PropTypes.bool,
};

export const PartnersForm = () => {
const [selectedPartner, setSelectedPartner] = useState({});
const [dateRange, setDateRange] = useState({
Expand All @@ -68,6 +82,7 @@ export const PartnersForm = () => {
const queryClient = useQueryClient();
const { id } = useParams();

// clear partnerNotSelectedError message when partner gets selected
useEffect(() => {
if (
selectedPartner &&
Expand All @@ -79,31 +94,25 @@ export const PartnersForm = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedPartner]);

// clear dateRange error messages when the right dates are picked
useEffect(() => {
if (!dateRange.endDate && errorMessage.id === messages.partnerEndDateError.id) {
setErrorMessage({});
return;
}

// clear error message if present when the selected endDate is after startDate
if (
dateRange.endDate &&
dateRange.startDate < dateRange.endDate &&
errorMessage.id === messages.partnerEndDateError.id
) {
setErrorMessage({});
return;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dateRange]);

const {
isPending,
isError,
data: partners,
} = useQuery({
queryKey: ['all-partners', userDetails.id],
queryFn: () => fetchLocalJSONAPI('partners/', token),
});
const { isPending, isError, data: partners } = useAllPartnersQuery(token, userDetails.id);

const savePartnerMutation = useMutation({
mutationFn: () => {
Expand Down Expand Up @@ -146,7 +155,7 @@ export const PartnersForm = () => {
}, [partners]);

const handleSave = () => {
if (!selectedPartner || !selectedPartner.id) {
if (!selectedPartner?.id) {
setErrorMessage(messages.partnerNotSelectedError);
return;
}
Expand Down
38 changes: 20 additions & 18 deletions frontend/src/components/projectEdit/partnersListing.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';

import { useSelector } from 'react-redux';
import ReactDatePicker from 'react-datepicker';
import { FormattedMessage } from 'react-intl';
Expand Down Expand Up @@ -57,11 +56,12 @@ export const Listing = ({ partnerIdToDetailsMapping }) => {
const queryClient = useQueryClient();

useEffect(() => {
if (actionType.length === 0) {
if (!actionType.length) {
setSelectedPartner({});
}
}, [actionType]);

// clear dateRange error messages when the right dates are picked
useEffect(() => {
const startDate = selectedPartner.startedOn && new Date(selectedPartner.startedOn);
const endDate = selectedPartner.endedOn && new Date(selectedPartner.endedOn);
Expand All @@ -71,9 +71,9 @@ export const Listing = ({ partnerIdToDetailsMapping }) => {
return;
}

// clear error message if present when the selected endDate is after startDate
if (endDate && startDate < endDate && errorMessage.id === messages.partnerEndDateError.id) {
setErrorMessage({});
return;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedPartner.startedOn, selectedPartner.endedOn]);
Expand All @@ -90,8 +90,7 @@ export const Listing = ({ partnerIdToDetailsMapping }) => {
const sortedPartnershipsByStartDate = response.partnerships.sort((itemA, itemB) => {
const dateA = new Date(itemA.startedOn);
const dateB = new Date(itemB.startedOn);
// return dateA - dateB; // Ascending order
return dateB - dateA; // Descending order
return dateB - dateA; // Descending order; Use dateA - dateB for ascending
});

return { partnerships: sortedPartnershipsByStartDate };
Expand Down Expand Up @@ -125,7 +124,7 @@ export const Listing = ({ partnerIdToDetailsMapping }) => {
: null;

return pushToLocalJSONAPI(
`projects/partnerships/${selectedPartner.id}`,
`projects/partnerships/${selectedPartner.id}/`,
JSON.stringify({
endedOn: endDate,
startedOn: startDate,
Expand Down Expand Up @@ -156,23 +155,26 @@ export const Listing = ({ partnerIdToDetailsMapping }) => {
updatePartnerMutation.mutate();
};

const getDateObjectAndDateString = (date) => {
const [year, month, day] = date.split('T')[0].split('-');
const dateString = `${day}/${month}/${year}`;
const dateObject = new Date(year, month - 1, day);
return [dateObject, dateString];
};

const isEmpty =
!isPending && !isRefetching && !isError && linkedPartners?.partnerships?.length === 0;

const tableContents = linkedPartners?.partnerships?.map((partner) => {
const [startYear, startMonth, startDay] = partner.startedOn.split('T')[0].split('-');
const startDateString = `${startDay}/${startMonth}/${startYear}`;
const startDate = new Date(startYear, startMonth - 1, startDay);
const [startDate, startDateString] = getDateObjectAndDateString(partner.startedOn)

let endDateString = 'N/A',
endDate = null;
if (partner.endedOn) {
const [endYear, endMonth, endDay] = partner.endedOn.split('T')[0].split('-');
endDateString = `${endDay}/${endMonth}/${endYear}`;
endDate = new Date(endYear, endMonth - 1, endDay);
[endDate, endDateString] = getDateObjectAndDateString(partner.endedOn)
}

const isInactive = endDate && endDate < new Date() ? true : false;
const isInactive = endDate && endDate < new Date();

return (
<tr
Expand Down Expand Up @@ -212,7 +214,7 @@ export const Listing = ({ partnerIdToDetailsMapping }) => {
return (
<div>
<div className="overflow-auto">
<table className="f6 w-100 mw8 center" cellspacing="0">
<table className="f6 w-100 mw8 center" cellSpacing="0">
<thead>
<tr>
<th className="fw6 f5 bb b--black-50 tl pb3 pr3">
Expand Down Expand Up @@ -274,9 +276,9 @@ export const Listing = ({ partnerIdToDetailsMapping }) => {
<FormattedMessage {...messages.partnerRemoveModalText} />
</p>

<dl class="lh-title mt0">
<dt class="f5 b">{partnerIdToDetailsMapping[selectedPartner.partnerId]?.name}</dt>
<dd class="ml0">
<dl className="lh-title mt0">
<dt className="f5 b">{partnerIdToDetailsMapping[selectedPartner.partnerId]?.name}</dt>
<dd className="ml0">
From&nbsp;
{selectedPartner.startedOn
? format(new Date(selectedPartner.startedOn), 'dd/MM/yyyy')
Expand Down Expand Up @@ -327,7 +329,7 @@ export const Listing = ({ partnerIdToDetailsMapping }) => {
<FormattedMessage {...messages.partnerUpdateModalTitle} />
</h3>
<div className="mt4 mb3 relative">
<h6 class="f5 b mb3 mt0">
<h6 className="f5 b mb3 mt0">
{partnerIdToDetailsMapping[selectedPartner.partnerId]?.name}
</h6>
<div className="flex items-center flex-wrap" style={{ gap: '1.75rem' }}>
Expand Down

0 comments on commit 6a18977

Please sign in to comment.