From fdc3f52e2146ff21accee122d5379a53fffe887f Mon Sep 17 00:00:00 2001 From: Chloengr Date: Tue, 20 Jun 2023 10:47:43 +0200 Subject: [PATCH 1/3] start finding meeting conflicts --- src/api/meeting.gql | 15 ++++ .../organisms/meeting/MeetingConfirmModal.tsx | 70 +++++++++++++++++ .../organisms/meeting/MeetingEditModal.tsx | 76 ++++++++++++------- src/graphql.generated.ts | 46 +++++++++++ src/hooks/useMatchMeetings.tsx | 55 ++++++++++++++ src/i18n/index.ts | 2 +- src/i18n/locales/en.json | 6 ++ src/i18n/locales/fr.json | 6 ++ 8 files changed, 246 insertions(+), 30 deletions(-) create mode 100644 src/components/organisms/meeting/MeetingConfirmModal.tsx create mode 100644 src/hooks/useMatchMeetings.tsx diff --git a/src/api/meeting.gql b/src/api/meeting.gql index 902adcc4..ae9f07bb 100644 --- a/src/api/meeting.gql +++ b/src/api/meeting.gql @@ -40,6 +40,21 @@ subscription circleMeetings($circleId: uuid!) { } } + +subscription meetingsAtSameTime($startDate: timestamptz!, $endDate: timestamptz!) { + meeting( + where: { _or: [{startDate: { _gte: $startDate }, startDate: { _lt: $startDate }}, { endDate: { _gt: $endDate }, endDate: { _lte: $endDate } } ]} + ) { + endDate + startDate + title + participantsMembersIds + participantsScope + circleId + } +} + + mutation createMeeting($values: meeting_insert_input!) { insert_meeting_one(object: $values) { ...Meeting diff --git a/src/components/organisms/meeting/MeetingConfirmModal.tsx b/src/components/organisms/meeting/MeetingConfirmModal.tsx new file mode 100644 index 00000000..81df1594 --- /dev/null +++ b/src/components/organisms/meeting/MeetingConfirmModal.tsx @@ -0,0 +1,70 @@ +import MemberLinkOverlay from '@atoms/MemberLinkOverlay' +import { + AlertDialog, + AlertDialogBody, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogOverlay, + AlertDialogProps, + Box, + Button, + Text, +} from '@chakra-ui/react' +import useMatchMeetings, { CreatedMeeting } from '@hooks/useMatchMeetings' +import React, { useRef } from 'react' +import { useTranslation } from 'react-i18next' + +interface Props + extends Omit { + meeting?: CreatedMeeting +} + +export default function MeetingConfirmModal({ meeting, ...alertProps }: Props) { + const { t } = useTranslation() + const cancelRef = useRef(null) + + const { matchingParticipants } = useMatchMeetings({ + currentMeeting: meeting!, + }) + + console.log({ matchingParticipants }) + + const handleCreate = () => { + alertProps.onClose() + } + + return ( + + + + + {t('MeetingConfirmModal.heading')} + + + + + {t('MeetingConfirmModal.info', { + count: matchingParticipants.length, + })} + + + {matchingParticipants.map((p) => ( + + ))} + + + + + + + + + + + ) +} diff --git a/src/components/organisms/meeting/MeetingEditModal.tsx b/src/components/organisms/meeting/MeetingEditModal.tsx index 53048dc1..6404d413 100644 --- a/src/components/organisms/meeting/MeetingEditModal.tsx +++ b/src/components/organisms/meeting/MeetingEditModal.tsx @@ -30,6 +30,7 @@ import { yupResolver } from '@hookform/resolvers/yup' import useCircle from '@hooks/useCircle' import useCreateMeeting from '@hooks/useCreateMeeting' import useCurrentMember from '@hooks/useCurrentMember' +import { CreatedMeeting } from '@hooks/useMatchMeetings' import { useOrgId } from '@hooks/useOrgId' import CircleFormController from '@molecules/circle/CircleFormController' import MeetingStepsConfigController, { @@ -38,11 +39,12 @@ import MeetingStepsConfigController, { import MeetingTemplateMenu from '@molecules/meeting/MeetingTemplateMenu' import VideoConfFormControl from '@molecules/meeting/VideoConfFormControl' import ParticipantsFormControl from '@molecules/ParticipantsFormControl' +import MeetingConfirmModal from '@organisms/meeting/MeetingConfirmModal' import { VideoConf, VideoConfTypes } from '@shared/model/meeting' import { nameSchema, stepsConfigSchema } from '@shared/schemas' import { getDateTimeLocal } from '@utils/dates' import { nanoid } from 'nanoid' -import React, { useMemo } from 'react' +import React, { useMemo, useState } from 'react' import { FormProvider, useForm } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { FiChevronDown } from 'react-icons/fi' @@ -98,6 +100,10 @@ export default function MeetingEditModal({ const createMeeting = useCreateMeeting() const [updateMeeting] = useUpdateMeetingMutation() + const [currentMeeting, setCurrentMeeting] = useState() + + const [confirmModal, setConfirmModal] = useState(false) + const defaultValues: Values = useMemo( () => ({ title: meeting?.title ?? '', @@ -168,7 +174,11 @@ export default function MeetingEditModal({ ...data }) => { if (!orgId || !currentMember || !circle) return + const startDateDate = new Date(startDate) + const endDateDate = new Date( + startDateDate.getTime() + duration * 60 * 1000 + ) const videoConf: VideoConf | null = videoConfType === VideoConfTypes.Url @@ -185,40 +195,41 @@ export default function MeetingEditModal({ const meetingUpdate = { ...data, startDate: startDateDate.toISOString(), - endDate: new Date( - startDateDate.getTime() + duration * 60 * 1000 - ).toISOString(), + endDate: endDateDate.toISOString(), participantsMembersIds: participantsMembersIds.map((m) => m.memberId), videoConf, } - if (meeting && !duplicate) { - // Update meeting - await updateMeeting({ - variables: { - id: meeting.id, - values: meetingUpdate, - }, - }) - } else { - // Create meeting - const result = await createMeeting( - { - orgId, - ...meetingUpdate, - }, - meeting && duplicate ? meeting.id : undefined - ) - if (!result) return + setCurrentMeeting(meetingUpdate) + setConfirmModal(true) - if (onCreate) { - onCreate(result.id) - } else { - navigate(result.path) - } - } + // if (meeting && !duplicate) { + // // Update meeting + // await updateMeeting({ + // variables: { + // id: meeting.id, + // values: meetingUpdate, + // }, + // }) + // } else { + // // Create meeting + // const result = await createMeeting( + // { + // orgId, + // ...meetingUpdate, + // }, + // meeting && duplicate ? meeting.id : undefined + // ) + // if (!result) return + + // if (onCreate) { + // onCreate(result.id) + // } else { + // navigate(result.path) + // } + // } - modalProps.onClose() + // modalProps.onClose() } ) @@ -331,6 +342,13 @@ export default function MeetingEditModal({ + {confirmModal && ( + setConfirmModal(false)} + /> + )} ) } diff --git a/src/graphql.generated.ts b/src/graphql.generated.ts index 2e5cca60..52d712ee 100644 --- a/src/graphql.generated.ts +++ b/src/graphql.generated.ts @@ -16523,6 +16523,14 @@ export type CircleMeetingsSubscriptionVariables = Exact<{ export type CircleMeetingsSubscription = { __typename?: 'subscription_root', meeting: Array<{ __typename?: 'meeting', id: string, orgId: string, circleId: string, participantsScope: Member_Scope_Enum, participantsMembersIds: Array, startDate: string, endDate: string, ended: boolean, title: string, currentStepId?: string | null }> }; +export type MeetingsAtSameTimeSubscriptionVariables = Exact<{ + startDate: Scalars['timestamptz']; + endDate: Scalars['timestamptz']; +}>; + + +export type MeetingsAtSameTimeSubscription = { __typename?: 'subscription_root', meeting: Array<{ __typename?: 'meeting', endDate: string, startDate: string, title: string, participantsMembersIds: Array, participantsScope: Member_Scope_Enum, circleId: string }> }; + export type CreateMeetingMutationVariables = Exact<{ values: Meeting_Insert_Input; }>; @@ -18154,6 +18162,44 @@ export function useCircleMeetingsSubscription(baseOptions: Apollo.SubscriptionHo } export type CircleMeetingsSubscriptionHookResult = ReturnType; export type CircleMeetingsSubscriptionResult = Apollo.SubscriptionResult; +export const MeetingsAtSameTimeDocument = gql` + subscription meetingsAtSameTime($startDate: timestamptz!, $endDate: timestamptz!) { + meeting( + where: {_or: [{startDate: {_gte: $startDate}, endDate: {_lte: $endDate}}, {startDate: {_lte: $startDate}, endDate: {_gt: $endDate}}]} + ) { + endDate + startDate + title + participantsMembersIds + participantsScope + circleId + } +} + `; + +/** + * __useMeetingsAtSameTimeSubscription__ + * + * To run a query within a React component, call `useMeetingsAtSameTimeSubscription` and pass it any options that fit your needs. + * When your component renders, `useMeetingsAtSameTimeSubscription` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useMeetingsAtSameTimeSubscription({ + * variables: { + * startDate: // value for 'startDate' + * endDate: // value for 'endDate' + * }, + * }); + */ +export function useMeetingsAtSameTimeSubscription(baseOptions: Apollo.SubscriptionHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSubscription(MeetingsAtSameTimeDocument, options); + } +export type MeetingsAtSameTimeSubscriptionHookResult = ReturnType; +export type MeetingsAtSameTimeSubscriptionResult = Apollo.SubscriptionResult; export const CreateMeetingDocument = gql` mutation createMeeting($values: meeting_insert_input!) { insert_meeting_one(object: $values) { diff --git a/src/hooks/useMatchMeetings.tsx b/src/hooks/useMatchMeetings.tsx new file mode 100644 index 00000000..bbf3af40 --- /dev/null +++ b/src/hooks/useMatchMeetings.tsx @@ -0,0 +1,55 @@ +// 1. find every meeting which are at the same time as the current meeting +// 2. find if the current meeting doesnt have same participant as the other meeting + +import { Meeting, useMeetingsAtSameTimeSubscription } from '@gql' +import { useOrgId } from '@hooks/useOrgId' +import useParticipants from '@hooks/useParticipants' + +export type CreatedMeeting = Pick< + Meeting, + | 'title' + | 'startDate' + | 'endDate' + | 'circleId' + | 'participantsScope' + | 'participantsMembersIds' +> + +interface Props { + currentMeeting: CreatedMeeting +} + +export default function useMatchMeetings({ currentMeeting }: Props) { + const orgId = useOrgId() + + console.log(currentMeeting.startDate, currentMeeting.endDate); + + // Subscribe to meetings + const { data } = useMeetingsAtSameTimeSubscription({ + skip: !orgId || !currentMeeting, + variables: { + startDate: currentMeeting.startDate.toString()!, + endDate: currentMeeting.endDate.toString()!, + }, + }) + + const currentMeetingParticipants = useParticipants( + currentMeeting.circleId, + currentMeeting.participantsScope, + currentMeeting.participantsMembersIds + ).map((p) => p.member) + + console.log({ data }) + + const dataMeetingsParticipants = useParticipants( + data?.meeting[0].circleId, + data?.meeting[0].participantsScope, + data?.meeting[0].participantsMembersIds + ).map((p) => p.member) + + const matchingParticipants = currentMeetingParticipants.filter((p) => + dataMeetingsParticipants.some((m) => m.id === p.id) + ) + + return { matchingParticipants } +} diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 099d4200..581d558a 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -35,7 +35,7 @@ i18n defaultNS, resources, interpolation: { - escapeValue: false, + escapeValue: false, }, }) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index b1c945a8..66717d92 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -233,6 +233,12 @@ "notInvited": "You are not invited to this meeting. Ask a participant to invite you if you want to join.", "logs": "Actions during the meeting" }, + "MeetingConfirmModal":{ + "heading": "Creation with conflicts", + "info":"Warning : the following user is not available on the chosen slot :", + "info_other":"Warning : the following users are not available on the chosen slot :", + "button": "Continue anyway" + }, "MeetingDeleteModal": { "heading": "Delete meeting", "info": "Are you sure you want to delete meeting {{name}}?" diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index a0618f86..09d9b69e 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -265,6 +265,12 @@ "notInvited": "Vous n'êtes pas invité(e) à cette réunion. Demandez à un(e) participant(e) de vous inviter si vous souhaitez la rejoindre.", "logs": "Actions pendant la réunion" }, + "MeetingConfirmModal":{ + "heading": "Création avec des conflits", + "info":"Attention : l'utilisateur suivant n'est pas disponible sur le créneau choisi :", + "info_other":"Attention : les utilisateurs suivants ne sont pas disponibles sur le créneau choisi :", + "button": "Créer quand même" + }, "MeetingDeleteModal": { "heading": "Supprimer une réunion", "info": "Êtes-vous sûr(e) de vouloir supprimer la réunion {{name}} ?" From 1e31f80fb667d567048c3305085f3c32d6b4969c Mon Sep 17 00:00:00 2001 From: Chloengr Date: Tue, 20 Jun 2023 10:47:43 +0200 Subject: [PATCH 2/3] confirm modal on non available members --- src/api/meeting.gql | 16 +-- src/components/atoms/MemberLinkOverlay.tsx | 4 +- .../organisms/meeting/MeetingConfirmModal.tsx | 35 ++++-- .../organisms/meeting/MeetingEditModal.tsx | 108 ++++++++++++------ src/graphql.generated.ts | 43 +++---- src/hooks/useMatchMeetings.tsx | 63 ++++------ src/i18n/index.ts | 2 +- src/i18n/locales/en.json | 6 +- src/i18n/locales/fr.json | 6 +- 9 files changed, 159 insertions(+), 124 deletions(-) diff --git a/src/api/meeting.gql b/src/api/meeting.gql index ae9f07bb..1143b574 100644 --- a/src/api/meeting.gql +++ b/src/api/meeting.gql @@ -41,16 +41,16 @@ subscription circleMeetings($circleId: uuid!) { } -subscription meetingsAtSameTime($startDate: timestamptz!, $endDate: timestamptz!) { +query meetingsAtSameTime($startDate: timestamptz!, $endDate: timestamptz!) { meeting( - where: { _or: [{startDate: { _gte: $startDate }, startDate: { _lt: $startDate }}, { endDate: { _gt: $endDate }, endDate: { _lte: $endDate } } ]} + where: { _or: [ + { startDate: { _gte: $startDate }, endDate: { _lte: $endDate } }, + { startDate: { _lte: $startDate }, endDate: { _gte: $endDate } }, + {_and: [{ startDate: { _lte: $startDate }, endDate: { _lte: $endDate } }, { endDate: { _gt: $startDate } }]}, + {_and: [{ startDate: { _gte: $startDate }, endDate: { _gte: $endDate } }, { startDate: { _lt: $endDate } }]} + ]} ) { - endDate - startDate - title - participantsMembersIds - participantsScope - circleId + ...MeetingSummary } } diff --git a/src/components/atoms/MemberLinkOverlay.tsx b/src/components/atoms/MemberLinkOverlay.tsx index 2df59d3e..6acc8105 100644 --- a/src/components/atoms/MemberLinkOverlay.tsx +++ b/src/components/atoms/MemberLinkOverlay.tsx @@ -1,11 +1,11 @@ import { Avatar, LinkOverlay, LinkOverlayProps } from '@chakra-ui/react' -import { MemberFragment } from '@gql' +import { MemberSummaryFragment } from '@gql' import useCircleMemberLink from '@hooks/useCircleMemberLink' import React from 'react' import { Link as ReachLink } from 'react-router-dom' interface Props extends LinkOverlayProps { - member: MemberFragment + member: MemberSummaryFragment } export default function MemberLinkOverlay({ diff --git a/src/components/organisms/meeting/MeetingConfirmModal.tsx b/src/components/organisms/meeting/MeetingConfirmModal.tsx index 81df1594..a6dfbab3 100644 --- a/src/components/organisms/meeting/MeetingConfirmModal.tsx +++ b/src/components/organisms/meeting/MeetingConfirmModal.tsx @@ -11,26 +11,41 @@ import { Button, Text, } from '@chakra-ui/react' -import useMatchMeetings, { CreatedMeeting } from '@hooks/useMatchMeetings' -import React, { useRef } from 'react' +import { MeetingSummaryFragment } from '@gql' +import useMatchMeetings from '@hooks/useMatchMeetings' +import { MeetingFormDataValues } from '@organisms/meeting/MeetingEditModal' +import React, { useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' interface Props extends Omit { - meeting?: CreatedMeeting + newMeeting?: MeetingFormDataValues + conflictedMeetings: MeetingSummaryFragment[] + onAccept: (currentMeeting: MeetingFormDataValues) => void } -export default function MeetingConfirmModal({ meeting, ...alertProps }: Props) { +export default function MeetingConfirmModal({ + newMeeting, + conflictedMeetings, + onAccept, + ...alertProps +}: Props) { const { t } = useTranslation() const cancelRef = useRef(null) - const { matchingParticipants } = useMatchMeetings({ - currentMeeting: meeting!, - }) + const { matchingParticipants } = useMatchMeetings( + newMeeting!, + conflictedMeetings + ) - console.log({ matchingParticipants }) + useEffect(() => { + if (matchingParticipants.length === 0) { + handleCreate() + } + }, [matchingParticipants.length]) const handleCreate = () => { + onAccept(newMeeting!) alertProps.onClose() } @@ -48,9 +63,9 @@ export default function MeetingConfirmModal({ meeting, ...alertProps }: Props) { count: matchingParticipants.length, })} - + {matchingParticipants.map((p) => ( - + ))} diff --git a/src/components/organisms/meeting/MeetingEditModal.tsx b/src/components/organisms/meeting/MeetingEditModal.tsx index 6404d413..2c64d3a1 100644 --- a/src/components/organisms/meeting/MeetingEditModal.tsx +++ b/src/components/organisms/meeting/MeetingEditModal.tsx @@ -22,15 +22,16 @@ import { import { Meeting_Step_Type_Enum, MeetingFragment, + MeetingSummaryFragment, MeetingTemplateFragment, Member_Scope_Enum, + useMeetingsAtSameTimeLazyQuery, useUpdateMeetingMutation, } from '@gql' import { yupResolver } from '@hookform/resolvers/yup' import useCircle from '@hooks/useCircle' import useCreateMeeting from '@hooks/useCreateMeeting' import useCurrentMember from '@hooks/useCurrentMember' -import { CreatedMeeting } from '@hooks/useMatchMeetings' import { useOrgId } from '@hooks/useOrgId' import CircleFormController from '@molecules/circle/CircleFormController' import MeetingStepsConfigController, { @@ -72,6 +73,11 @@ interface Values extends StepsValues { videoConfUrl: string } +export type MeetingFormDataValues = Omit< + MeetingSummaryFragment, + 'id' | 'orgId' | 'ended' +> + const resolver = yupResolver( yup.object().shape({ title: nameSchema.required(), @@ -100,8 +106,10 @@ export default function MeetingEditModal({ const createMeeting = useCreateMeeting() const [updateMeeting] = useUpdateMeetingMutation() - const [currentMeeting, setCurrentMeeting] = useState() - + const [newMeeting, setNewMeeting] = useState() + const [conflictingMeetings, setConflictingMeetings] = useState< + MeetingSummaryFragment[] + >([]) const [confirmModal, setConfirmModal] = useState(false) const defaultValues: Values = useMemo( @@ -144,6 +152,7 @@ export default function MeetingEditModal({ resolver, defaultValues, }) + const { handleSubmit, register, @@ -157,12 +166,65 @@ export default function MeetingEditModal({ const circleId = watch('circleId') const circle = useCircle(circleId) + const [checkConflictingMeetings] = useMeetingsAtSameTimeLazyQuery() + // Template change const handleTemplateSelect = (template: MeetingTemplateFragment) => { setValue('title', template.title) setValue('stepsConfig', template.stepsConfig) } + const checkOccupation = async (meetingUpdate: MeetingFormDataValues) => { + try { + const { data } = await checkConflictingMeetings({ + variables: { + startDate: meetingUpdate.startDate?.toString()!, + endDate: meetingUpdate.endDate?.toString()!, + }, + }) + if (data && data.meeting && !!data.meeting.length) { + setNewMeeting(meetingUpdate) + setConflictingMeetings(data.meeting) + setConfirmModal(true) + return true + } else { + return false + } + } catch (error) { + console.error(error) + } + } + + const handleConfirm = async (meetingUpdate: MeetingFormDataValues) => { + if (meeting && !duplicate) { + // Update meeting + await updateMeeting({ + variables: { + id: meeting.id, + values: meetingUpdate, + }, + }) + } else { + // Create meeting + const result = await createMeeting( + { + orgId, + ...meetingUpdate, + }, + meeting && duplicate ? meeting.id : undefined + ) + if (!result) return + + if (onCreate) { + onCreate(result.id) + } else { + navigate(result.path) + } + } + + modalProps.onClose() + } + // Submit const onSubmit = handleSubmit( async ({ @@ -200,36 +262,12 @@ export default function MeetingEditModal({ videoConf, } - setCurrentMeeting(meetingUpdate) - setConfirmModal(true) - - // if (meeting && !duplicate) { - // // Update meeting - // await updateMeeting({ - // variables: { - // id: meeting.id, - // values: meetingUpdate, - // }, - // }) - // } else { - // // Create meeting - // const result = await createMeeting( - // { - // orgId, - // ...meetingUpdate, - // }, - // meeting && duplicate ? meeting.id : undefined - // ) - // if (!result) return - - // if (onCreate) { - // onCreate(result.id) - // } else { - // navigate(result.path) - // } - // } - - // modalProps.onClose() + console.log('meetingUpdate', meetingUpdate) + + if (await checkOccupation(meetingUpdate)) { + return + } + handleConfirm(meetingUpdate) } ) @@ -344,9 +382,11 @@ export default function MeetingEditModal({ {confirmModal && ( setConfirmModal(false)} + onAccept={handleConfirm} /> )} diff --git a/src/graphql.generated.ts b/src/graphql.generated.ts index 52d712ee..02f54aa7 100644 --- a/src/graphql.generated.ts +++ b/src/graphql.generated.ts @@ -16523,13 +16523,13 @@ export type CircleMeetingsSubscriptionVariables = Exact<{ export type CircleMeetingsSubscription = { __typename?: 'subscription_root', meeting: Array<{ __typename?: 'meeting', id: string, orgId: string, circleId: string, participantsScope: Member_Scope_Enum, participantsMembersIds: Array, startDate: string, endDate: string, ended: boolean, title: string, currentStepId?: string | null }> }; -export type MeetingsAtSameTimeSubscriptionVariables = Exact<{ +export type MeetingsAtSameTimeQueryVariables = Exact<{ startDate: Scalars['timestamptz']; endDate: Scalars['timestamptz']; }>; -export type MeetingsAtSameTimeSubscription = { __typename?: 'subscription_root', meeting: Array<{ __typename?: 'meeting', endDate: string, startDate: string, title: string, participantsMembersIds: Array, participantsScope: Member_Scope_Enum, circleId: string }> }; +export type MeetingsAtSameTimeQuery = { __typename?: 'query_root', meeting: Array<{ __typename?: 'meeting', id: string, orgId: string, circleId: string, participantsScope: Member_Scope_Enum, participantsMembersIds: Array, startDate: string, endDate: string, ended: boolean, title: string, currentStepId?: string | null }> }; export type CreateMeetingMutationVariables = Exact<{ values: Meeting_Insert_Input; @@ -18163,43 +18163,46 @@ export function useCircleMeetingsSubscription(baseOptions: Apollo.SubscriptionHo export type CircleMeetingsSubscriptionHookResult = ReturnType; export type CircleMeetingsSubscriptionResult = Apollo.SubscriptionResult; export const MeetingsAtSameTimeDocument = gql` - subscription meetingsAtSameTime($startDate: timestamptz!, $endDate: timestamptz!) { + query meetingsAtSameTime($startDate: timestamptz!, $endDate: timestamptz!) { meeting( - where: {_or: [{startDate: {_gte: $startDate}, endDate: {_lte: $endDate}}, {startDate: {_lte: $startDate}, endDate: {_gt: $endDate}}]} + where: {_or: [{startDate: {_gte: $startDate}, endDate: {_lte: $endDate}}, {startDate: {_lte: $startDate}, endDate: {_gte: $endDate}}, {_and: [{startDate: {_lte: $startDate}, endDate: {_lte: $endDate}}, {endDate: {_gt: $startDate}}]}, {_and: [{startDate: {_gte: $startDate}, endDate: {_gte: $endDate}}, {startDate: {_lt: $endDate}}]}]} ) { - endDate - startDate - title - participantsMembersIds - participantsScope - circleId + ...MeetingSummary } } - `; + ${MeetingSummaryFragmentDoc}`; /** - * __useMeetingsAtSameTimeSubscription__ + * __useMeetingsAtSameTimeQuery__ * - * To run a query within a React component, call `useMeetingsAtSameTimeSubscription` and pass it any options that fit your needs. - * When your component renders, `useMeetingsAtSameTimeSubscription` returns an object from Apollo Client that contains loading, error, and data properties + * To run a query within a React component, call `useMeetingsAtSameTimeQuery` and pass it any options that fit your needs. + * When your component renders, `useMeetingsAtSameTimeQuery` returns an object from Apollo Client that contains loading, error, and data properties * you can use to render your UI. * - * @param baseOptions options that will be passed into the subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; * * @example - * const { data, loading, error } = useMeetingsAtSameTimeSubscription({ + * const { data, loading, error } = useMeetingsAtSameTimeQuery({ * variables: { * startDate: // value for 'startDate' * endDate: // value for 'endDate' * }, * }); */ -export function useMeetingsAtSameTimeSubscription(baseOptions: Apollo.SubscriptionHookOptions) { +export function useMeetingsAtSameTimeQuery(baseOptions: Apollo.QueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useSubscription(MeetingsAtSameTimeDocument, options); + return Apollo.useQuery(MeetingsAtSameTimeDocument, options); } -export type MeetingsAtSameTimeSubscriptionHookResult = ReturnType; -export type MeetingsAtSameTimeSubscriptionResult = Apollo.SubscriptionResult; +export function useMeetingsAtSameTimeLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(MeetingsAtSameTimeDocument, options); + } +export type MeetingsAtSameTimeQueryHookResult = ReturnType; +export type MeetingsAtSameTimeLazyQueryHookResult = ReturnType; +export type MeetingsAtSameTimeQueryResult = Apollo.QueryResult; +export function refetchMeetingsAtSameTimeQuery(variables: MeetingsAtSameTimeQueryVariables) { + return { query: MeetingsAtSameTimeDocument, variables: variables } + } export const CreateMeetingDocument = gql` mutation createMeeting($values: meeting_insert_input!) { insert_meeting_one(object: $values) { diff --git a/src/hooks/useMatchMeetings.tsx b/src/hooks/useMatchMeetings.tsx index bbf3af40..d9e1f31e 100644 --- a/src/hooks/useMatchMeetings.tsx +++ b/src/hooks/useMatchMeetings.tsx @@ -1,54 +1,31 @@ -// 1. find every meeting which are at the same time as the current meeting -// 2. find if the current meeting doesnt have same participant as the other meeting - -import { Meeting, useMeetingsAtSameTimeSubscription } from '@gql' -import { useOrgId } from '@hooks/useOrgId' +import { MeetingSummaryFragment } from '@gql' import useParticipants from '@hooks/useParticipants' +import { MeetingFormDataValues } from '@organisms/meeting/MeetingEditModal' -export type CreatedMeeting = Pick< - Meeting, - | 'title' - | 'startDate' - | 'endDate' - | 'circleId' - | 'participantsScope' - | 'participantsMembersIds' -> - -interface Props { - currentMeeting: CreatedMeeting -} - -export default function useMatchMeetings({ currentMeeting }: Props) { - const orgId = useOrgId() - - console.log(currentMeeting.startDate, currentMeeting.endDate); - - // Subscribe to meetings - const { data } = useMeetingsAtSameTimeSubscription({ - skip: !orgId || !currentMeeting, - variables: { - startDate: currentMeeting.startDate.toString()!, - endDate: currentMeeting.endDate.toString()!, - }, - }) +export default function useMatchMeetings( + newMeeting?: MeetingFormDataValues, + conflictedMeetings?: MeetingSummaryFragment[] +) { + if (!newMeeting || !conflictedMeetings) { + return { matchingParticipants: [] } + } const currentMeetingParticipants = useParticipants( - currentMeeting.circleId, - currentMeeting.participantsScope, - currentMeeting.participantsMembersIds + newMeeting.circleId, + newMeeting.participantsScope, + newMeeting.participantsMembersIds ).map((p) => p.member) - console.log({ data }) - - const dataMeetingsParticipants = useParticipants( - data?.meeting[0].circleId, - data?.meeting[0].participantsScope, - data?.meeting[0].participantsMembersIds - ).map((p) => p.member) + const dataMeetingsParticipants = conflictedMeetings.map((meeting) => { + return useParticipants( + meeting.circleId, + meeting.participantsScope, + meeting.participantsMembersIds + ).map((p) => p.member) + }) const matchingParticipants = currentMeetingParticipants.filter((p) => - dataMeetingsParticipants.some((m) => m.id === p.id) + dataMeetingsParticipants.some((m) => m.find((mp) => mp.id === p.id)) ) return { matchingParticipants } diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 581d558a..099d4200 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -35,7 +35,7 @@ i18n defaultNS, resources, interpolation: { - escapeValue: false, + escapeValue: false, }, }) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 66717d92..dc4e3e02 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -233,10 +233,10 @@ "notInvited": "You are not invited to this meeting. Ask a participant to invite you if you want to join.", "logs": "Actions during the meeting" }, - "MeetingConfirmModal":{ + "MeetingConfirmModal": { "heading": "Creation with conflicts", - "info":"Warning : the following user is not available on the chosen slot :", - "info_other":"Warning : the following users are not available on the chosen slot :", + "info": "Warning, the following user is not available on the chosen slot :", + "info_other": "Warning, the following users are not available on the chosen slot :", "button": "Continue anyway" }, "MeetingDeleteModal": { diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 09d9b69e..4e370428 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -265,10 +265,10 @@ "notInvited": "Vous n'êtes pas invité(e) à cette réunion. Demandez à un(e) participant(e) de vous inviter si vous souhaitez la rejoindre.", "logs": "Actions pendant la réunion" }, - "MeetingConfirmModal":{ + "MeetingConfirmModal": { "heading": "Création avec des conflits", - "info":"Attention : l'utilisateur suivant n'est pas disponible sur le créneau choisi :", - "info_other":"Attention : les utilisateurs suivants ne sont pas disponibles sur le créneau choisi :", + "info": "Attention, l'utilisateur suivant n'est pas disponible sur le créneau choisi :", + "info_other": "Attention, les utilisateurs suivants ne sont pas disponibles sur le créneau choisi :", "button": "Créer quand même" }, "MeetingDeleteModal": { From 9777e5ad5548d8bfdf80011e4f5a5db1df355755 Mon Sep 17 00:00:00 2001 From: Chloengr Date: Tue, 20 Jun 2023 17:36:38 +0200 Subject: [PATCH 3/3] handle every interval --- src/api/meeting.gql | 18 +++++++++--------- .../organisms/meeting/MeetingEditModal.tsx | 6 ++---- src/graphql.generated.ts | 15 ++++++++------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/api/meeting.gql b/src/api/meeting.gql index 1143b574..9a009d0c 100644 --- a/src/api/meeting.gql +++ b/src/api/meeting.gql @@ -41,16 +41,16 @@ subscription circleMeetings($circleId: uuid!) { } -query meetingsAtSameTime($startDate: timestamptz!, $endDate: timestamptz!) { - meeting( - where: { _or: [ - { startDate: { _gte: $startDate }, endDate: { _lte: $endDate } }, - { startDate: { _lte: $startDate }, endDate: { _gte: $endDate } }, - {_and: [{ startDate: { _lte: $startDate }, endDate: { _lte: $endDate } }, { endDate: { _gt: $startDate } }]}, - {_and: [{ startDate: { _gte: $startDate }, endDate: { _gte: $endDate } }, { startDate: { _lt: $endDate } }]} - ]} - ) { +query meetingsAtSameTime($NMstartDate: timestamptz!, $NMendDate: timestamptz!) { + meeting(where: { archived: {_eq: false}, + _or: [ + { startDate: { _gt: $NMstartDate }, endDate: { _lt: $NMendDate } }, + { startDate: { _lte: $NMstartDate }, endDate: { _gte: $NMendDate } }, + {_and: [{ startDate: { _lte: $NMstartDate }, endDate: { _gte: $NMstartDate } }, { endDate: { _lte: $NMendDate } }]}, + {_and: [{ startDate: { _gte: $NMstartDate }, endDate: { _gte: $NMendDate } }, { startDate: { _lt: $NMendDate } }]} + ]}) { ...MeetingSummary + archived } } diff --git a/src/components/organisms/meeting/MeetingEditModal.tsx b/src/components/organisms/meeting/MeetingEditModal.tsx index 2c64d3a1..0e21c835 100644 --- a/src/components/organisms/meeting/MeetingEditModal.tsx +++ b/src/components/organisms/meeting/MeetingEditModal.tsx @@ -178,8 +178,8 @@ export default function MeetingEditModal({ try { const { data } = await checkConflictingMeetings({ variables: { - startDate: meetingUpdate.startDate?.toString()!, - endDate: meetingUpdate.endDate?.toString()!, + NMstartDate: meetingUpdate.startDate?.toString()!, + NMendDate: meetingUpdate.endDate?.toString()!, }, }) if (data && data.meeting && !!data.meeting.length) { @@ -262,8 +262,6 @@ export default function MeetingEditModal({ videoConf, } - console.log('meetingUpdate', meetingUpdate) - if (await checkOccupation(meetingUpdate)) { return } diff --git a/src/graphql.generated.ts b/src/graphql.generated.ts index 02f54aa7..72b2bf47 100644 --- a/src/graphql.generated.ts +++ b/src/graphql.generated.ts @@ -16524,12 +16524,12 @@ export type CircleMeetingsSubscriptionVariables = Exact<{ export type CircleMeetingsSubscription = { __typename?: 'subscription_root', meeting: Array<{ __typename?: 'meeting', id: string, orgId: string, circleId: string, participantsScope: Member_Scope_Enum, participantsMembersIds: Array, startDate: string, endDate: string, ended: boolean, title: string, currentStepId?: string | null }> }; export type MeetingsAtSameTimeQueryVariables = Exact<{ - startDate: Scalars['timestamptz']; - endDate: Scalars['timestamptz']; + NMstartDate: Scalars['timestamptz']; + NMendDate: Scalars['timestamptz']; }>; -export type MeetingsAtSameTimeQuery = { __typename?: 'query_root', meeting: Array<{ __typename?: 'meeting', id: string, orgId: string, circleId: string, participantsScope: Member_Scope_Enum, participantsMembersIds: Array, startDate: string, endDate: string, ended: boolean, title: string, currentStepId?: string | null }> }; +export type MeetingsAtSameTimeQuery = { __typename?: 'query_root', meeting: Array<{ __typename?: 'meeting', archived: boolean, id: string, orgId: string, circleId: string, participantsScope: Member_Scope_Enum, participantsMembersIds: Array, startDate: string, endDate: string, ended: boolean, title: string, currentStepId?: string | null }> }; export type CreateMeetingMutationVariables = Exact<{ values: Meeting_Insert_Input; @@ -18163,11 +18163,12 @@ export function useCircleMeetingsSubscription(baseOptions: Apollo.SubscriptionHo export type CircleMeetingsSubscriptionHookResult = ReturnType; export type CircleMeetingsSubscriptionResult = Apollo.SubscriptionResult; export const MeetingsAtSameTimeDocument = gql` - query meetingsAtSameTime($startDate: timestamptz!, $endDate: timestamptz!) { + query meetingsAtSameTime($NMstartDate: timestamptz!, $NMendDate: timestamptz!) { meeting( - where: {_or: [{startDate: {_gte: $startDate}, endDate: {_lte: $endDate}}, {startDate: {_lte: $startDate}, endDate: {_gte: $endDate}}, {_and: [{startDate: {_lte: $startDate}, endDate: {_lte: $endDate}}, {endDate: {_gt: $startDate}}]}, {_and: [{startDate: {_gte: $startDate}, endDate: {_gte: $endDate}}, {startDate: {_lt: $endDate}}]}]} + where: {archived: {_eq: false}, _or: [{startDate: {_gt: $NMstartDate}, endDate: {_lt: $NMendDate}}, {startDate: {_lte: $NMstartDate}, endDate: {_gte: $NMendDate}}, {_and: [{startDate: {_lte: $NMstartDate}, endDate: {_gte: $NMstartDate}}, {endDate: {_lte: $NMendDate}}]}, {_and: [{startDate: {_gte: $NMstartDate}, endDate: {_gte: $NMendDate}}, {startDate: {_lt: $NMendDate}}]}]} ) { ...MeetingSummary + archived } } ${MeetingSummaryFragmentDoc}`; @@ -18184,8 +18185,8 @@ export const MeetingsAtSameTimeDocument = gql` * @example * const { data, loading, error } = useMeetingsAtSameTimeQuery({ * variables: { - * startDate: // value for 'startDate' - * endDate: // value for 'endDate' + * NMstartDate: // value for 'NMstartDate' + * NMendDate: // value for 'NMendDate' * }, * }); */