diff --git a/back/extensions/documentation/documentation/1.0.0/full_documentation.json b/back/extensions/documentation/documentation/1.0.0/full_documentation.json index bfc8ed1e..a5563b59 100644 --- a/back/extensions/documentation/documentation/1.0.0/full_documentation.json +++ b/back/extensions/documentation/documentation/1.0.0/full_documentation.json @@ -14,7 +14,7 @@ "name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html" }, - "x-generation-date": "03/07/2024 4:37:03 PM" + "x-generation-date": "03/10/2024 10:56:56 PM" }, "x-strapi-config": { "path": "/documentation", diff --git a/web/components/Account/AccountMenu.tsx b/web/components/Account/AccountMenu.tsx index 4d9cd3ee..195df344 100644 --- a/web/components/Account/AccountMenu.tsx +++ b/web/components/Account/AccountMenu.tsx @@ -155,7 +155,8 @@ const AccountMenu = ({ user }: { user: UsersPermissionsUser }) => { (user?.type === 'place' && placeCampaigns?.length && applications?.length) || - (user?.type === 'company' && currentCampaign) + (user?.type === 'company' && currentCampaign && currentCampaign.mode !== "closed") + const displayMenu = ({ title, items, translationParams = {} }) => { const isDisactivated = !isComplete && title === 'dashboard' diff --git a/web/components/Account/Application/Company/ApplicationCompanyList.tsx b/web/components/Account/Application/Company/ApplicationCompanyList.tsx index 2a78cc89..1d59a5a0 100644 --- a/web/components/Account/Application/Company/ApplicationCompanyList.tsx +++ b/web/components/Account/Application/Company/ApplicationCompanyList.tsx @@ -59,6 +59,7 @@ const ApplicationCompanyList = ({ applications = [] }: Props) => { query?.disponibility as string, ]) queryClient.refetchQueries(['me']) + onClose() } catch (e) { errorToast(t('company.delete_error')) } diff --git a/web/components/Account/Application/Place/ApplicationDownloadAll.tsx b/web/components/Account/Application/Place/ApplicationDownloadAll.tsx index 44e09ab0..85b282a7 100644 --- a/web/components/Account/Application/Place/ApplicationDownloadAll.tsx +++ b/web/components/Account/Application/Place/ApplicationDownloadAll.tsx @@ -1,18 +1,14 @@ import { Box, Button } from '@chakra-ui/react' import { useTranslation } from 'next-i18next' import { useRouter } from 'next/router' -import { useState } from 'react' import { useMyApplications } from '~hooks/useMyApplications' import useSelectedCampaign from '~hooks/useSelectedCampaign' -import useToast from '~hooks/useToast' -import { handleDisponibilityDownload } from '~utils/pdf' const ApplicationDownloadAll = () => { const { selectedCampaign } = useSelectedCampaign() - const { errorToast } = useToast() const { t } = useTranslation('application') const { query } = useRouter() - const [isDownloading, setIsDownloading] = useState(false) + const { data: applications, isLoading, isFetching } = useMyApplications({ name: ['myApplications', query?.disponibility as string], searchParams: { ...query, _sort: 'company.structureName:asc' }, @@ -28,31 +24,18 @@ const ApplicationDownloadAll = () => { return ( diff --git a/web/components/Account/Application/Place/DetailDrawer/ApplicationDetailDrawer.tsx b/web/components/Account/Application/Place/DetailDrawer/ApplicationDetailDrawer.tsx index e1a53ccd..70e2cda9 100644 --- a/web/components/Account/Application/Place/DetailDrawer/ApplicationDetailDrawer.tsx +++ b/web/components/Account/Application/Place/DetailDrawer/ApplicationDetailDrawer.tsx @@ -16,11 +16,12 @@ import ApplicationDetailHeader from '~components/Account/Application/Place/Detai import { Application } from '~typings/api' import ApplicationRightPanel from '~components/Account/Application/Place/DetailDrawer/ApplicationRightPanel' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { Document, Page } from 'react-pdf' import useToast from '~hooks/useToast' import { handleApplicationDownload } from '~utils/pdf' +import ApplicationDetails from '~components/Account/Application/Place/DetailDrawer/ApplicationDetails' const ApplicationDetailDrawer = ({ isOpen, @@ -35,6 +36,7 @@ const ApplicationDetailDrawer = ({ }) => { const { t } = useTranslation('application') const { id } = application ?? {} + const [displayPdf, setDisplayPdf] = useState(false) const [isDownloading, setIsDownloading] = useState(false) const [scales, setScales] = useState([]) const { errorToast } = useToast() @@ -55,27 +57,38 @@ const ApplicationDetailDrawer = ({ } } + useEffect(() => { + setTimeout(() => { + setDisplayPdf(true) + }, 1500) + }, [id, isOpen]) + if (!application) { return null } return ( - + { + setDisplayPdf(false) + onClose() + }} + size="xl" + > {t('place.detail.title', { id })} - - @@ -86,47 +99,52 @@ const ApplicationDetailDrawer = ({ display="flex" height="100%" width="100%" + flexDirection="column" > - { - setNumPages(numPages) - }} - loading={ - - } - style={{ height: '100%' }} - > - {Array.from(new Array(numPages), (el, index) => ( - { - const viewport = page.getViewport({ scale: 1 }) - if (viewport) { - const orientation = - viewport.width > viewport.height - ? 'landscape' - : 'portrait' - // Adjust the scale based on the orientation - const scale = orientation === 'landscape' ? 0.7 : 1.0 - setScales((prevScales) => { - const newScales = [...prevScales] - newScales[index] = scale - return newScales - }) - } - }} - scale={scales[index] || 1.0} - /> - ))} - + + + {displayPdf && ( + { + setNumPages(numPages) + }} + loading={ + + } + style={{ height: '100%' }} + > + {Array.from(new Array(numPages), (_el, index) => ( + { + const viewport = page.getViewport({ scale: 1 }) + if (viewport) { + const orientation = + viewport.width > viewport.height + ? 'landscape' + : 'portrait' + // Adjust the scale based on the orientation + const scale = + orientation === 'landscape' ? 0.7 : 1.0 + setScales((prevScales) => { + const newScales = [...prevScales] + newScales[index] = scale + return newScales + }) + } + }} + scale={scales[index] || 1.0} + /> + ))} + + )} - { + const { t } = useTranslation('place') + + return ( + + + + } + > + {application?.references?.map((reference: Reference, index) => ( + + + {t('campaignApplication.references.references_creation', { + index: index + 1, + })} + + {`${reference?.title}, ${reference?.year}`} + + {t('campaignApplication.references.references_actors_display', { + number: reference?.actors, + })} + + {(Boolean(reference?.other) + ? [reference?.partners, reference?.other] + : reference?.partners + )?.join(', ')} + + + + ))} + + + + + } spacing={4} mb={10}> + + {Boolean(application?.already_supported) ? 'Oui' : 'Non'} + + + {application?.cv} + + + + + + } spacing={4} mb={10}> + + {application?.creation_title} + + + {application?.creation_dancers} + + + {application?.creation_summary} + + + {application?.creation_partnerships} + + + {application?.creation_techical_requirements} + + + {Boolean(application?.creation_accomodation) + ? t('campaignApplication.creation.yes') + : t('campaignApplication.creation.no')} + + + + + ) +} + +export default ApplicationDetails diff --git a/web/components/Account/Application/Place/DetailDrawer/ApplicationSection.tsx b/web/components/Account/Application/Place/DetailDrawer/ApplicationSection.tsx new file mode 100644 index 00000000..dc3f6405 --- /dev/null +++ b/web/components/Account/Application/Place/DetailDrawer/ApplicationSection.tsx @@ -0,0 +1,17 @@ +import { Text, VStack } from '@chakra-ui/react' +import { ReactNode } from 'react-markdown' + +const ApplicationSection = ({ + label, + children, +}: { + label: ReactNode + children: ReactNode +}) => ( + + {label} + {children} + +) + +export default ApplicationSection diff --git a/web/package.json b/web/package.json index c6e167ba..392f4dfb 100644 --- a/web/package.json +++ b/web/package.json @@ -25,6 +25,7 @@ "@mapbox/mapbox-sdk": "^0.12.1", "@react-pdf/renderer": "1.6.17", "@svgr/webpack": "^6.2.1", + "adm-zip": "^0.5.10", "axios": "^0.21.1", "babel-plugin-transform-require-ignore": "^0.1.1", "date-fns": "^2.19.0", diff --git a/web/pages/api/pdfs/all/[id].tsx b/web/pages/api/pdfs/all/[id].tsx index 9233c930..41e11cde 100644 --- a/web/pages/api/pdfs/all/[id].tsx +++ b/web/pages/api/pdfs/all/[id].tsx @@ -3,8 +3,8 @@ import { renderToStream } from '@react-pdf/renderer' import { client } from '~api/client-api' import ApplicationDocument from '~components/pdfs/ApplicationDocument' import { getSession } from 'next-auth/client' -import PDFMerger from 'pdf-merger-js' -import { formatDisponibilityPdfName, getBufferFromStream } from '~utils/pdf' +import { formatDisponibilityZipName, getBufferFromStream } from '~utils/pdf' +import AdmZip from "adm-zip" const MultipleApplication = async (req, res) => { const { id: disponibilityId } = req.query @@ -31,34 +31,34 @@ const MultipleApplication = async (req, res) => { const disponibility = applications?.[0]?.disponibility const campaign = applications?.[0]?.campaign - const merger = new PDFMerger() - let finalPDF + const zip = new AdmZip(); for (const application of applications) { + const name = application.company?.structureName; const stream = await renderToStream( , ) const streamBuffer = await getBufferFromStream(stream) - await merger.add(streamBuffer) + await zip.addFile(`${name}/candidature.pdf`, streamBuffer); if (application?.creation_file?.[0]?.url) { const creationFile = await fetch(application?.creation_file?.[0]?.url) - const creationFileArrayBuffer = await creationFile.arrayBuffer() - - await merger.add(creationFileArrayBuffer) + + // @ts-ignore + const creationFileArrayBuffer = await creationFile.buffer() + await zip.addFile(`${name}/dossier-artistique.pdf`, creationFileArrayBuffer); } } - finalPDF = await merger.saveAsBuffer() + const zipBuffer = zip.toBuffer(); - res.setHeader('Content-Type', 'application/pdf') + res.setHeader('Content-Type', 'application/zip') res.setHeader( 'Content-Disposition', - 'attachment; filename=' + - // @ts-expect-error - formatDisponibilityPdfName(disponibility, campaign), + // @ts-expect-error + 'attachment; filename=' + formatDisponibilityZipName(disponibility, campaign), ) - res.send(finalPDF) + res.send(zipBuffer) } export default MultipleApplication diff --git a/web/pages/api/pdfs/selected/[id].tsx b/web/pages/api/pdfs/selected/[id].tsx index 8ded303f..bee73cf1 100644 --- a/web/pages/api/pdfs/selected/[id].tsx +++ b/web/pages/api/pdfs/selected/[id].tsx @@ -3,9 +3,8 @@ import { renderToStream } from '@react-pdf/renderer' import { client } from '~api/client-api' import ApplicationDocument from '~components/pdfs/ApplicationDocument' import { getSession } from 'next-auth/client' -import PDFMerger from 'pdf-merger-js' -import { formatCampaignPdfName, getBufferFromStream } from '~utils/pdf' -import DividerPage from '~components/pdfs/DividerPage' +import { formatCampaignZipName, getBufferFromStream } from '~utils/pdf' +import AdmZip from "adm-zip" const SelectedCampaignApplications = async (req, res) => { const { id: campaignId } = req.query @@ -17,7 +16,7 @@ const SelectedCampaignApplications = async (req, res) => { return } - let finalPDF + const zip = new AdmZip(); const { data: campaign } = await client.campaigns.campaignsDetail(campaignId) try { @@ -54,42 +53,37 @@ const SelectedCampaignApplications = async (req, res) => { groupedApplications[userId].sort((a, b) => { const aId = a.disponibility?.id || 0 const bId = b.disponibility?.id || 0 + return aId - bId }) } - const merger = new PDFMerger() - const userIds = Object.keys(groupedApplications) for (const userId of userIds) { const applications = groupedApplications[userId] const place = applications[0]?.disponibility?.espace?.users_permissions_user - - const dividerStream = await renderToStream( - , - ) - const dividerStreamBufffer = await getBufferFromStream(dividerStream) - await merger.add(dividerStreamBufffer) - + const name = place?.structureName + + for (const application of applications) { const stream = await renderToStream( , - ) + ) + const streamBuffer = await getBufferFromStream(stream) - await merger.add(streamBuffer) + await zip.addFile(`${name}/candidature.pdf`, streamBuffer); if (application?.creation_file?.[0]?.url) { const creationFile = await fetch(application?.creation_file?.[0]?.url) - const creationFileArrayBuffer = await creationFile.arrayBuffer() - - await merger.add(creationFileArrayBuffer) + // @ts-ignore + const creationFileArrayBuffer = await creationFile.buffer() + + await zip.addFile(`${name}/dossier-artistique.pdf`, creationFileArrayBuffer); } } } - - finalPDF = await merger.saveAsBuffer() } catch (error) { console.error(error) res @@ -98,12 +92,14 @@ const SelectedCampaignApplications = async (req, res) => { return } - res.setHeader('Content-Type', 'application/pdf') + const zipBuffer = zip.toBuffer(); + + res.setHeader('Content-Type', 'application/zip') res.setHeader( 'Content-Disposition', - 'attachment; filename=' + formatCampaignPdfName(campaign), + 'attachment; filename=' + formatCampaignZipName(campaign), ) - res.send(finalPDF) + res.send(zipBuffer) } export default SelectedCampaignApplications diff --git a/web/pages/compte/mes-candidatures/index.tsx b/web/pages/compte/mes-candidatures/index.tsx index 1bb5167b..c9618542 100644 --- a/web/pages/compte/mes-candidatures/index.tsx +++ b/web/pages/compte/mes-candidatures/index.tsx @@ -36,7 +36,15 @@ const CompanyApplications = ({ user }: Props) => { } }, [currentCampaign]) - if (!currentCampaign) return null + if (!currentCampaign) { + return null + } + + if (currentCampaign?.mode === "closed") { + router.push(ROUTE_ACCOUNT) + return null + } + return ( diff --git a/web/public/locales/fr/place.json b/web/public/locales/fr/place.json index 7ba354c4..02613dc2 100644 --- a/web/public/locales/fr/place.json +++ b/web/public/locales/fr/place.json @@ -465,7 +465,7 @@ }, "general": { "title": " Informations générales", - "subtitle": "Avez-vous déjà été soutenu·e par {{place}}?", + "subtitle": "Avez-vous déjà été soutenu·e par {{place}} ?", "bio": "C.V. | Biographie du / de la chorégraphe", "bioHelper": "(3000 signes maximum)" }, diff --git a/web/theme/index.ts b/web/theme/index.ts index 02624d0f..9e6936f4 100644 --- a/web/theme/index.ts +++ b/web/theme/index.ts @@ -104,6 +104,11 @@ const theme = extendTheme({ Divider, Input, Textarea, + Drawer: { + sizes: { + xl: { dialog: { maxW: '65rem' } }, + }, + }, }, textStyles: { h1: { diff --git a/web/utils/pdf.ts b/web/utils/pdf.ts index 1b84e996..2f34dd4d 100644 --- a/web/utils/pdf.ts +++ b/web/utils/pdf.ts @@ -47,7 +47,7 @@ export const handleApplicationDownload = async ({ link.parentNode?.removeChild(link) } -export const formatDisponibilityPdfName = ( +export const formatDisponibilityZipName = ( disponibility: Disponibility, campaign: Campaign, ) => { @@ -60,35 +60,9 @@ export const formatDisponibilityPdfName = ( //@ts-expect-error )}_${disponibility?.espace?.users_permissions_user?.structureName ?.split(' ') - .join('_')}_${campaign?.title?.split(' ').join('_')}.pdf` + .join('_')}_${campaign?.title?.split(' ').join('_')}.zip` } -export const handleDisponibilityDownload = async ({ - disponibility, - campaign, - onError, -}: { - disponibility: Disponibility - campaign: Campaign - onError: () => void -}) => { - const res = await fetch(`/api/pdfs/all/${disponibility.id}`) - if (!res.ok) { - onError() - } - const blob = await res.blob() - const url = window.URL.createObjectURL(blob) - const link = document.createElement('a') - link.href = url - link.setAttribute( - 'download', - formatDisponibilityPdfName(disponibility, campaign), - ) - document.body.appendChild(link) - link.click() - link.parentNode?.removeChild(link) -} - -export const formatCampaignPdfName = (campaign: Campaign) => { - return `${campaign?.title?.split(' ').join('_')}.pdf` +export const formatCampaignZipName = (campaign: Campaign) => { + return `${campaign?.title?.split(' ').join('_')}.zip` } diff --git a/web/yarn.lock b/web/yarn.lock index d1cb988a..b35e0ac1 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -2453,6 +2453,11 @@ resolved "https://registry.yarnpkg.com/@types/yoga-layout/-/yoga-layout-1.9.7.tgz#cb60565e69af9d132cac420a4bb94e6472847f6f" integrity sha512-TlYNIa9XHCGYRqVrijiDVj72Sc4Yd9At0NYEaHm8Su94GwcsXRq5y1AhA8tZUVNXYRCNpGWuOWI4VQ+R58MgUw== +adm-zip@^0.5.10: + version "0.5.10" + resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.10.tgz#4a51d5ab544b1f5ce51e1b9043139b639afff45b" + integrity sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ== + ajv-keywords@^3.1.0, ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"