richie-education 2.25.0-b2.dev20 → 2.25.0-b2.dev22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/js/api/joanie.ts +9 -6
- package/js/components/ContractFrame/OrganizationContractFrame.tsx +1 -1
- package/js/hooks/useContracts/index.tsx +42 -3
- package/js/hooks/useTeacherPendingContractsCount/index.ts +51 -0
- package/js/pages/DashboardContracts/index.tsx +2 -2
- package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.tsx +6 -9
- package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractsToSign.tsx +7 -10
- package/js/types/Joanie.ts +5 -0
- package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.spec.tsx +4 -3
- package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.tsx +13 -14
- package/package.json +1 -1
package/js/api/joanie.ts
CHANGED
|
@@ -370,12 +370,9 @@ const API = (): Joanie.API => {
|
|
|
370
370
|
contracts: {
|
|
371
371
|
get: async ({ contract_ids, ...filters } = {}) => {
|
|
372
372
|
const endpointFilters = { ...filters, queryParameters: { id: contract_ids } };
|
|
373
|
-
return fetchWithJWT(
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
: buildApiUrl(ROUTES.user.contracts.get, filters),
|
|
377
|
-
{ method: 'GET' },
|
|
378
|
-
).then(checkStatus);
|
|
373
|
+
return fetchWithJWT(buildApiUrl(ROUTES.user.contracts.get, endpointFilters), {
|
|
374
|
+
method: 'GET',
|
|
375
|
+
}).then(checkStatus);
|
|
379
376
|
},
|
|
380
377
|
download(id: string): Promise<any> {
|
|
381
378
|
return fetchWithJWT(ROUTES.user.contracts.download.replace(':id', id), {
|
|
@@ -391,6 +388,12 @@ const API = (): Joanie.API => {
|
|
|
391
388
|
}).then(checkStatus);
|
|
392
389
|
},
|
|
393
390
|
contracts: {
|
|
391
|
+
get: async ({ contract_ids, ...filters } = {}) => {
|
|
392
|
+
const endpointFilters = { ...filters, queryParameters: { id: contract_ids } };
|
|
393
|
+
return fetchWithJWT(buildApiUrl(ROUTES.organizations.contracts.get, endpointFilters), {
|
|
394
|
+
method: 'GET',
|
|
395
|
+
}).then(checkStatus);
|
|
396
|
+
},
|
|
394
397
|
getSignatureLinks: async (filters) => {
|
|
395
398
|
return fetchWithJWT(
|
|
396
399
|
buildApiUrl(ROUTES.organizations.contracts.getSignatureLinks, filters),
|
|
@@ -33,7 +33,7 @@ const OrganizationContractFrame = ({ organizationId, contractIds, onDone, ...pro
|
|
|
33
33
|
* Check if all contracts to signed has been signed.
|
|
34
34
|
*/
|
|
35
35
|
const checkContractsSignature = async () => {
|
|
36
|
-
const { results: contractsToCheck } = await api.
|
|
36
|
+
const { results: contractsToCheck } = await api.organizations.contracts.get({
|
|
37
37
|
organization_id: organizationId,
|
|
38
38
|
contract_ids: contractIdsToCheck,
|
|
39
39
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineMessages } from 'react-intl';
|
|
2
2
|
import { useJoanieApi } from 'contexts/JoanieApiContext';
|
|
3
|
-
import { useResource, useResources, UseResourcesProps } from 'hooks/useResources';
|
|
3
|
+
import { QueryOptions, useResource, useResources, UseResourcesProps } from 'hooks/useResources';
|
|
4
4
|
import { API, Contract, ContractFilters } from 'types/Joanie';
|
|
5
5
|
|
|
6
6
|
const messages = defineMessages({
|
|
@@ -22,8 +22,47 @@ const props: UseResourcesProps<Contract, ContractFilters, API['user']['contracts
|
|
|
22
22
|
session: true,
|
|
23
23
|
messages,
|
|
24
24
|
};
|
|
25
|
+
|
|
25
26
|
/**
|
|
26
27
|
* Joanie Api hook to retrieve/update a contract owned by the authenticated user.
|
|
27
28
|
*/
|
|
28
|
-
export const
|
|
29
|
-
export const
|
|
29
|
+
export const useUserContract = useResource(props);
|
|
30
|
+
export const useUserContracts = useResources(props);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Joanie Api hook to retrieve/update a contracts related to a course.
|
|
34
|
+
*/
|
|
35
|
+
const organizationProps: UseResourcesProps<
|
|
36
|
+
Contract,
|
|
37
|
+
ContractFilters,
|
|
38
|
+
API['organizations']['contracts']
|
|
39
|
+
> = {
|
|
40
|
+
...props,
|
|
41
|
+
queryKey: ['organization_contracts'],
|
|
42
|
+
apiInterface: () => useJoanieApi().organizations.contracts,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const useOrganizationContract = (
|
|
46
|
+
id: string,
|
|
47
|
+
filters: ContractFilters,
|
|
48
|
+
queryOptions?: QueryOptions<Contract>,
|
|
49
|
+
) => {
|
|
50
|
+
return useResource(organizationProps)(id, filters, {
|
|
51
|
+
...queryOptions,
|
|
52
|
+
enabled:
|
|
53
|
+
!!id &&
|
|
54
|
+
!!filters?.organization_id &&
|
|
55
|
+
(queryOptions?.enabled === undefined || queryOptions.enabled),
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const useOrganizationContracts = (
|
|
60
|
+
filters: ContractFilters,
|
|
61
|
+
queryOptions?: QueryOptions<Contract>,
|
|
62
|
+
) => {
|
|
63
|
+
return useResources(organizationProps)(filters, {
|
|
64
|
+
...queryOptions,
|
|
65
|
+
enabled:
|
|
66
|
+
!!filters?.organization_id && (queryOptions?.enabled === undefined || queryOptions.enabled),
|
|
67
|
+
});
|
|
68
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useOrganizationContracts } from 'hooks/useContracts';
|
|
3
|
+
import { useCourseProductRelation } from 'hooks/useCourseProductRelation';
|
|
4
|
+
import { PER_PAGE } from 'settings';
|
|
5
|
+
import { ContractState, CourseProductRelation, Organization } from 'types/Joanie';
|
|
6
|
+
|
|
7
|
+
interface UseTeacherPendingContractsCountProps {
|
|
8
|
+
organizationId?: Organization['id'];
|
|
9
|
+
courseProductRelationId?: CourseProductRelation['id'];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const useTeacherPendingContractsCount = ({
|
|
13
|
+
organizationId,
|
|
14
|
+
courseProductRelationId,
|
|
15
|
+
}: UseTeacherPendingContractsCountProps) => {
|
|
16
|
+
const {
|
|
17
|
+
item: training,
|
|
18
|
+
states: { isFetched: isTrainingFetched },
|
|
19
|
+
} = useCourseProductRelation(courseProductRelationId, {
|
|
20
|
+
organization_id: organizationId,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const isActive = useMemo(() => {
|
|
24
|
+
return !!(organizationId && (!courseProductRelationId || isTrainingFetched));
|
|
25
|
+
}, [organizationId, courseProductRelationId, isTrainingFetched]);
|
|
26
|
+
|
|
27
|
+
const { items: contracts, meta } = useOrganizationContracts(
|
|
28
|
+
{
|
|
29
|
+
organization_id: organizationId,
|
|
30
|
+
course_id: training?.course.id,
|
|
31
|
+
product_id: training?.product.id,
|
|
32
|
+
signature_state: ContractState.LEARNER_SIGNED,
|
|
33
|
+
page: 1,
|
|
34
|
+
page_size: PER_PAGE.teacherContractList,
|
|
35
|
+
},
|
|
36
|
+
{ enabled: isActive },
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
if (isActive) {
|
|
40
|
+
return {
|
|
41
|
+
contracts,
|
|
42
|
+
pendingContractCount: meta?.pagination?.count ?? 0,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
contracts: [],
|
|
47
|
+
pendingContractCount: 0,
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default useTeacherPendingContractsCount;
|
|
@@ -4,7 +4,7 @@ import { Pagination, usePagination } from 'components/Pagination';
|
|
|
4
4
|
import { Spinner } from 'components/Spinner';
|
|
5
5
|
import Banner, { BannerType } from 'components/Banner';
|
|
6
6
|
import { DashboardItemContract } from 'widgets/Dashboard/components/DashboardItem/Contract';
|
|
7
|
-
import {
|
|
7
|
+
import { useUserContracts } from 'hooks/useContracts';
|
|
8
8
|
import { NestedCredentialOrder } from 'types/Joanie';
|
|
9
9
|
|
|
10
10
|
const messages = defineMessages({
|
|
@@ -27,7 +27,7 @@ export const DashboardContracts = () => {
|
|
|
27
27
|
items: contracts,
|
|
28
28
|
meta,
|
|
29
29
|
states: { error, fetching },
|
|
30
|
-
} =
|
|
30
|
+
} = useUserContracts({
|
|
31
31
|
page: pagination.currentPage,
|
|
32
32
|
page_size: pagination.itemsPerPage,
|
|
33
33
|
});
|
|
@@ -4,7 +4,7 @@ import { DataGrid, usePagination } from '@openfun/cunningham-react';
|
|
|
4
4
|
import { useEffect, useMemo } from 'react';
|
|
5
5
|
import { useParams, useSearchParams } from 'react-router-dom';
|
|
6
6
|
import { ContractHelper, ContractStatePoV } from 'utils/ContractHelper';
|
|
7
|
-
import {
|
|
7
|
+
import { useOrganizationContracts } from 'hooks/useContracts';
|
|
8
8
|
import Banner, { BannerType } from 'components/Banner';
|
|
9
9
|
import { PER_PAGE } from 'settings';
|
|
10
10
|
import { ContractFilters } from 'types/Joanie';
|
|
@@ -47,14 +47,11 @@ const TeacherDashboardContracts = () => {
|
|
|
47
47
|
items: contracts,
|
|
48
48
|
meta,
|
|
49
49
|
states: { fetching, isFetched, error },
|
|
50
|
-
} =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
},
|
|
56
|
-
{ enabled: !!filters.organization_id },
|
|
57
|
-
);
|
|
50
|
+
} = useOrganizationContracts({
|
|
51
|
+
...filters,
|
|
52
|
+
page: pagination.page,
|
|
53
|
+
page_size: PER_PAGE.teacherContractList,
|
|
54
|
+
});
|
|
58
55
|
|
|
59
56
|
const rows = useMemo(() => {
|
|
60
57
|
return contracts.map((contract) => ({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import useContractAbilities from 'hooks/useContractAbilities';
|
|
2
|
-
import {
|
|
2
|
+
import { useOrganizationContracts } from 'hooks/useContracts';
|
|
3
3
|
import { ContractState, CourseListItem, Organization, Product } from 'types/Joanie';
|
|
4
4
|
import { ContractActions } from 'utils/AbilitiesHelper/types';
|
|
5
5
|
|
|
@@ -14,15 +14,12 @@ const useTeacherContractsToSign = ({
|
|
|
14
14
|
productId,
|
|
15
15
|
organizationId,
|
|
16
16
|
}: UseTeacherContractsToSignProps) => {
|
|
17
|
-
const { items: contractsToSign, meta: contractsToSignMeta } =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
},
|
|
24
|
-
{ enabled: !!organizationId },
|
|
25
|
-
);
|
|
17
|
+
const { items: contractsToSign, meta: contractsToSignMeta } = useOrganizationContracts({
|
|
18
|
+
signature_state: ContractState.LEARNER_SIGNED,
|
|
19
|
+
organization_id: organizationId,
|
|
20
|
+
course_id: courseId,
|
|
21
|
+
product_id: productId,
|
|
22
|
+
});
|
|
26
23
|
const contractAbilities = useContractAbilities(contractsToSign);
|
|
27
24
|
const contractsToSignCount = contractsToSignMeta?.pagination?.count ?? 0;
|
|
28
25
|
|
package/js/types/Joanie.ts
CHANGED
|
@@ -541,6 +541,11 @@ export interface API {
|
|
|
541
541
|
filters?: Filters,
|
|
542
542
|
): Filters extends { id: string } ? Promise<Nullable<Organization>> : Promise<Organization[]>;
|
|
543
543
|
contracts: {
|
|
544
|
+
get(
|
|
545
|
+
filters?: ContractFilters,
|
|
546
|
+
): ContractFilters extends { id: string }
|
|
547
|
+
? Promise<Nullable<Contract>>
|
|
548
|
+
: Promise<PaginatedResponse<Contract>>;
|
|
544
549
|
getSignatureLinks(
|
|
545
550
|
filters?: OrganizationContractSignatureLinksFilters,
|
|
546
551
|
): Promise<OrganizationContractInvitationLinkResponse>;
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import JoanieSessionProvider from 'contexts/SessionContext/JoanieSessionProvider';
|
|
15
15
|
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
|
|
16
16
|
import { ContractFactory, OrganizationFactory } from 'utils/test/factories/joanie';
|
|
17
|
+
import { PER_PAGE } from 'settings';
|
|
17
18
|
import { TeacherDashboardOrganizationSidebar } from '.';
|
|
18
19
|
|
|
19
20
|
jest.mock('utils/context', () => ({
|
|
@@ -61,7 +62,7 @@ describe('<TeacherDashboardOrganizationSidebar />', () => {
|
|
|
61
62
|
fetchMock.get(
|
|
62
63
|
'https://joanie.endpoint/api/v1.0/organizations/' +
|
|
63
64
|
organization.id +
|
|
64
|
-
|
|
65
|
+
`/contracts/?signature_state=half_signed&page=1&page_size=${PER_PAGE.teacherContractList}`,
|
|
65
66
|
{ results: [], count: 0, previous: null, next: null },
|
|
66
67
|
);
|
|
67
68
|
|
|
@@ -96,7 +97,7 @@ describe('<TeacherDashboardOrganizationSidebar />', () => {
|
|
|
96
97
|
fetchMock.get(
|
|
97
98
|
'https://joanie.endpoint/api/v1.0/organizations/' +
|
|
98
99
|
organization.id +
|
|
99
|
-
|
|
100
|
+
`/contracts/?signature_state=half_signed&page=1&page_size=${PER_PAGE.teacherContractList}`,
|
|
100
101
|
{
|
|
101
102
|
results: ContractFactory({ abilities: { sign: true } }).many(contractToSignCount),
|
|
102
103
|
count: contractToSignCount,
|
|
@@ -128,7 +129,7 @@ describe('<TeacherDashboardOrganizationSidebar />', () => {
|
|
|
128
129
|
fetchMock.get(
|
|
129
130
|
'https://joanie.endpoint/api/v1.0/organizations/' +
|
|
130
131
|
organization.id +
|
|
131
|
-
|
|
132
|
+
`/contracts/?signature_state=half_signed&page=1&page_size=${PER_PAGE.teacherContractList}`,
|
|
132
133
|
{
|
|
133
134
|
results: ContractFactory({ abilities: { sign: false } }).many(contractToSignCount),
|
|
134
135
|
count: contractToSignCount,
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|
3
|
-
import { createSearchParams,
|
|
3
|
+
import { createSearchParams, useParams } from 'react-router-dom';
|
|
4
4
|
import Badge from 'components/Badge';
|
|
5
5
|
import { Spinner } from 'components/Spinner';
|
|
6
|
-
import useContractAbilities from 'hooks/useContractAbilities';
|
|
7
|
-
import { useContracts } from 'hooks/useContracts';
|
|
8
6
|
import { useOrganization } from 'hooks/useOrganizations';
|
|
9
7
|
import { ContractState } from 'types/Joanie';
|
|
10
|
-
import { ContractActions } from 'utils/AbilitiesHelper/types';
|
|
11
8
|
import { DashboardSidebar, MenuLink } from 'widgets/Dashboard/components/DashboardSidebar';
|
|
12
9
|
import {
|
|
13
10
|
getDashboardRouteLabel,
|
|
14
11
|
getDashboardRoutePath,
|
|
15
12
|
} from 'widgets/Dashboard/utils/dashboardRoutes';
|
|
16
13
|
import { TeacherDashboardPaths } from 'widgets/Dashboard/utils/teacherRouteMessages';
|
|
14
|
+
import useTeacherPendingContractsCount from 'hooks/useTeacherPendingContractsCount';
|
|
15
|
+
import useContractAbilities from 'hooks/useContractAbilities';
|
|
16
|
+
import { ContractActions } from 'utils/AbilitiesHelper/types';
|
|
17
17
|
import { DashboardAvatar, DashboardAvatarVariantEnum } from '../DashboardAvatar';
|
|
18
18
|
|
|
19
19
|
const messages = defineMessages({
|
|
@@ -33,25 +33,24 @@ export const TeacherDashboardOrganizationSidebar = () => {
|
|
|
33
33
|
const intl = useIntl();
|
|
34
34
|
const getRoutePath = getDashboardRoutePath(intl);
|
|
35
35
|
const getRouteLabel = getDashboardRouteLabel(intl);
|
|
36
|
-
const { organizationId } = useParams<{
|
|
36
|
+
const { organizationId, productId: courseProductRelationId } = useParams<{
|
|
37
|
+
organizationId: string;
|
|
38
|
+
productId?: string;
|
|
39
|
+
}>();
|
|
37
40
|
const {
|
|
38
41
|
item: organization,
|
|
39
42
|
states: { fetching },
|
|
40
43
|
} = useOrganization(organizationId);
|
|
41
44
|
|
|
42
|
-
const {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
const { contracts: pendingContracts, pendingContractCount } = useTeacherPendingContractsCount({
|
|
46
|
+
organizationId,
|
|
47
|
+
courseProductRelationId,
|
|
45
48
|
});
|
|
46
|
-
const contractAbilities = useContractAbilities(
|
|
47
|
-
|
|
48
|
-
const pendingContractCount = meta?.pagination?.count ?? 0;
|
|
49
|
+
const contractAbilities = useContractAbilities(pendingContracts);
|
|
49
50
|
const canSignContracts = contractAbilities.can(ContractActions.SIGN);
|
|
50
51
|
|
|
51
52
|
const getMenuLinkFromPath = (basePath: TeacherDashboardPaths) => {
|
|
52
|
-
const path =
|
|
53
|
-
organizationId,
|
|
54
|
-
});
|
|
53
|
+
const path = getRoutePath(basePath, { organizationId });
|
|
55
54
|
|
|
56
55
|
const menuLink: MenuLink = {
|
|
57
56
|
to: path,
|