Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/api/meeting.gql
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ subscription circleMeetings($circleId: uuid!) {
}
}


query meetingsAtSameTime($NMstartDate: timestamptz!, $NMendDate: timestamptz!) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je vais te proposer quelques changements de nomenclature pour mieux s'y retrouver.
Ici, renommer en getOverlappingMeetings

meeting(where: { archived: {_eq: false},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il manque orgId. Là ça compare avec les réus de toutes les orgs.

_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 } }]}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu dois pouvoir faire plus simple et performant avec la fonction postgres OVERLAPS.
https://database.guide/how-to-test-for-overlapping-dates-in-postgresql/

Je ne crois pas qu'Hasura supporte nativement cette fonction, donc tu peux créer une fonction Hasura getOverlappingMeetings :
https://hasura.io/docs/latest/schema/postgres/custom-functions/

]}) {
...MeetingSummary
archived
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pas besoin de récupérer archived

}
}


mutation createMeeting($values: meeting_insert_input!) {
insert_meeting_one(object: $values) {
...Meeting
Expand Down
4 changes: 2 additions & 2 deletions src/components/atoms/MemberLinkOverlay.tsx
Original file line number Diff line number Diff line change
@@ -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({
Expand Down
85 changes: 85 additions & 0 deletions src/components/organisms/meeting/MeetingConfirmModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import MemberLinkOverlay from '@atoms/MemberLinkOverlay'
import {
AlertDialog,
AlertDialogBody,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
AlertDialogProps,
Box,
Button,
Text,
} from '@chakra-ui/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<AlertDialogProps, 'children' | 'leastDestructiveRef'> {
newMeeting?: MeetingFormDataValues
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newMeeting ne devrait pas pouvoir être undefined. Ca te simplifiera les usages dans le composant.

conflictedMeetings: MeetingSummaryFragment[]
onAccept: (currentMeeting: MeetingFormDataValues) => void
}

export default function MeetingConfirmModal({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renommer en MeetingOverlapModal

newMeeting,
conflictedMeetings,
onAccept,
...alertProps
}: Props) {
const { t } = useTranslation()
const cancelRef = useRef<HTMLButtonElement>(null)

const { matchingParticipants } = useMatchMeetings(
newMeeting!,
conflictedMeetings
)

useEffect(() => {
if (matchingParticipants.length === 0) {
handleCreate()
}
}, [matchingParticipants.length])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ce check devrait être fait après le calcul de l'overlap, pas dans ce composant. Sinon on ouvre et ferme la modale instantanément, c'est pas super propre.


const handleCreate = () => {
onAccept(newMeeting!)
alertProps.onClose()
}

return (
<AlertDialog {...alertProps} leastDestructiveRef={cancelRef}>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader>
{t('MeetingConfirmModal.heading')}
</AlertDialogHeader>

<AlertDialogBody>
<Text>
{t('MeetingConfirmModal.info', {
count: matchingParticipants.length,
})}
</Text>
<Box px={2} py={1} gap="3">
{matchingParticipants.map((p) => (
<MemberLinkOverlay key={p.id} member={p} mt="4" />
))}
</Box>
</AlertDialogBody>

<AlertDialogFooter>
<Button ref={cancelRef} onClick={alertProps.onClose}>
{t('common.cancel')}
</Button>
<Button colorScheme="red" onClick={handleCreate} ml={3}>
{t('MeetingConfirmModal.button')}
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Peux-tu mettre dans la description de la PR une capture d'écran ou une vidéo qui montre la modale stp ?

)
}
116 changes: 86 additions & 30 deletions src/components/organisms/meeting/MeetingEditModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import {
import {
Meeting_Step_Type_Enum,
MeetingFragment,
MeetingSummaryFragment,
MeetingTemplateFragment,
Member_Scope_Enum,
useMeetingsAtSameTimeLazyQuery,
useUpdateMeetingMutation,
} from '@gql'
import { yupResolver } from '@hookform/resolvers/yup'
Expand All @@ -38,11 +40,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'
Expand Down Expand Up @@ -70,6 +73,11 @@ interface Values extends StepsValues {
videoConfUrl: string
}

export type MeetingFormDataValues = Omit<
MeetingSummaryFragment,
'id' | 'orgId' | 'ended'
>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu dois pouvoir utiliser l'interface Values à la place


const resolver = yupResolver(
yup.object().shape({
title: nameSchema.required(),
Expand Down Expand Up @@ -98,6 +106,12 @@ export default function MeetingEditModal({
const createMeeting = useCreateMeeting()
const [updateMeeting] = useUpdateMeetingMutation()

const [newMeeting, setNewMeeting] = useState<MeetingFormDataValues>()
const [conflictingMeetings, setConflictingMeetings] = useState<
MeetingSummaryFragment[]
>([])
const [confirmModal, setConfirmModal] = useState(false)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pour faire pareil que pour les autres modales et pour renommer en overlapModal :

const overlapModal = useDisclosure()


const defaultValues: Values = useMemo(
() => ({
title: meeting?.title ?? '',
Expand Down Expand Up @@ -138,6 +152,7 @@ export default function MeetingEditModal({
resolver,
defaultValues,
})

const {
handleSubmit,
register,
Expand All @@ -151,12 +166,65 @@ export default function MeetingEditModal({
const circleId = watch('circleId')
const circle = useCircle(circleId)

const [checkConflictingMeetings] = useMeetingsAtSameTimeLazyQuery()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renommer en :
const [getOverlappingMeetings] = useGetOverlappingMeetings()


// 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: {
NMstartDate: meetingUpdate.startDate?.toString()!,
NMendDate: 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)
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu peux réintégrer cette fonction dans onSubmit, ça sera plus lisible à mon avis.


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 ({
Expand All @@ -168,7 +236,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
Expand All @@ -185,40 +257,15 @@ 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

if (onCreate) {
onCreate(result.id)
} else {
navigate(result.path)
}
if (await checkOccupation(meetingUpdate)) {
return
}

modalProps.onClose()
handleConfirm(meetingUpdate)
}
)

Expand Down Expand Up @@ -331,6 +378,15 @@ export default function MeetingEditModal({
</form>
</ModalContent>
</Modal>
{confirmModal && (
<MeetingConfirmModal
isOpen
newMeeting={newMeeting}
conflictedMeetings={conflictingMeetings}
onClose={() => setConfirmModal(false)}
onAccept={handleConfirm}
/>
)}
</FormProvider>
)
}
Expand Down
50 changes: 50 additions & 0 deletions src/graphql.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>, startDate: string, endDate: string, ended: boolean, title: string, currentStepId?: string | null }> };

export type MeetingsAtSameTimeQueryVariables = Exact<{
NMstartDate: Scalars['timestamptz'];
NMendDate: Scalars['timestamptz'];
}>;


export type MeetingsAtSameTimeQuery = { __typename?: 'query_root', meeting: Array<{ __typename?: 'meeting', archived: boolean, id: string, orgId: string, circleId: string, participantsScope: Member_Scope_Enum, participantsMembersIds: Array<string>, startDate: string, endDate: string, ended: boolean, title: string, currentStepId?: string | null }> };

export type CreateMeetingMutationVariables = Exact<{
values: Meeting_Insert_Input;
}>;
Expand Down Expand Up @@ -18154,6 +18162,48 @@ export function useCircleMeetingsSubscription(baseOptions: Apollo.SubscriptionHo
}
export type CircleMeetingsSubscriptionHookResult = ReturnType<typeof useCircleMeetingsSubscription>;
export type CircleMeetingsSubscriptionResult = Apollo.SubscriptionResult<CircleMeetingsSubscription>;
export const MeetingsAtSameTimeDocument = gql`
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
}
}
${MeetingSummaryFragmentDoc}`;

/**
* __useMeetingsAtSameTimeQuery__
*
* 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 query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useMeetingsAtSameTimeQuery({
* variables: {
* NMstartDate: // value for 'NMstartDate'
* NMendDate: // value for 'NMendDate'
* },
* });
*/
export function useMeetingsAtSameTimeQuery(baseOptions: Apollo.QueryHookOptions<MeetingsAtSameTimeQuery, MeetingsAtSameTimeQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<MeetingsAtSameTimeQuery, MeetingsAtSameTimeQueryVariables>(MeetingsAtSameTimeDocument, options);
}
export function useMeetingsAtSameTimeLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MeetingsAtSameTimeQuery, MeetingsAtSameTimeQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<MeetingsAtSameTimeQuery, MeetingsAtSameTimeQueryVariables>(MeetingsAtSameTimeDocument, options);
}
export type MeetingsAtSameTimeQueryHookResult = ReturnType<typeof useMeetingsAtSameTimeQuery>;
export type MeetingsAtSameTimeLazyQueryHookResult = ReturnType<typeof useMeetingsAtSameTimeLazyQuery>;
export type MeetingsAtSameTimeQueryResult = Apollo.QueryResult<MeetingsAtSameTimeQuery, MeetingsAtSameTimeQueryVariables>;
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) {
Expand Down
Loading