From 9df0dc03b95d79062f6405440b8102d2dc006fb2 Mon Sep 17 00:00:00 2001 From: lucieo Date: Wed, 21 Feb 2024 13:38:47 +0100 Subject: [PATCH] Basis for applications place listing --- .../application/controllers/application.js | 5 +- .../documentation/1.0.0/application.json | 172 ++++++++------- .../models/application.settings.json | 8 +- .../booking/documentation/1.0.0/booking.json | 6 + .../documentation/1.0.0/campaign.json | 4 +- back/api/city/documentation/1.0.0/city.json | 6 + .../documentation/1.0.0/disponibility.json | 10 +- .../espace/documentation/1.0.0/espace.json | 76 +++++++ back/api/espace/models/espace.settings.json | 4 + .../1.0.0/full_documentation.json | 200 ++++++++++-------- .../1.0.0/users-permissions-User.json | 10 +- .../Company/ApplicationCompanyList.tsx | 2 + .../Company/ApplicationCompanyListItem.tsx | 8 +- .../Place/ApplicationPlaceList.tsx | 33 +-- .../Place/ApplicationPlaceListItem.tsx | 56 ++--- .../Application/Place/ApplicationSelector.tsx | 19 ++ web/components/Campaign/CampaignTag.tsx | 6 +- .../Places/Application/ApplicationForm.tsx | 3 +- web/components/Place/PlaceGridCard.tsx | 10 +- web/pages/compte/candidatures/index.tsx | 2 +- web/public/locales/fr/account.json | 4 +- web/public/locales/fr/application.json | 2 +- 22 files changed, 395 insertions(+), 251 deletions(-) create mode 100644 web/components/Account/Application/Place/ApplicationSelector.tsx diff --git a/back/api/application/controllers/application.js b/back/api/application/controllers/application.js index 7a9664e9..4f39e1bd 100644 --- a/back/api/application/controllers/application.js +++ b/back/api/application/controllers/application.js @@ -8,7 +8,8 @@ module.exports = { async myApplications(ctx) { const { id, type } = ctx.state.user; - const query = type === "place" ? { place: id } : { company: id }; + const query = type === "place" ? { 'disponibility.espace.users_permissions_user.id':id} : { company: id }; + const populate = type==="place"? ['disponibility.espace','company']:['disponibility.espace', 'place','disponibility.espace.users_permissions_user'] return strapi .query("application") @@ -17,7 +18,7 @@ module.exports = { ...query, _sort: "disponibility.start:desc", }, - ['disponibility.espace', 'place'] + populate ) .then((res) => { return Promise.all( diff --git a/back/api/application/documentation/1.0.0/application.json b/back/api/application/documentation/1.0.0/application.json index 7dbf2094..9de5ea80 100644 --- a/back/api/application/documentation/1.0.0/application.json +++ b/back/api/application/documentation/1.0.0/application.json @@ -864,7 +864,7 @@ } } }, - "place": { + "company": { "required": [ "id", "email", @@ -1018,147 +1018,141 @@ } } }, - "company": { + "espace": { "required": [ "id", - "email", - "username", - "firstname", - "lastname", - "structureName", + "name", + "surface", + "roomLength", + "width", + "height", + "mirror", + "danceBar", + "accomodation", + "technicalStaff", + "floor", "address", - "zipCode", - "city", "country", - "siret", - "ape", - "phone", - "license", - "type" + "latitude" ], "properties": { "id": { "type": "string" }, - "email": { - "type": "string" - }, - "provider": { + "name": { "type": "string" }, - "password": { - "type": "string" + "surface": { + "type": "number" }, - "resetPasswordToken": { - "type": "string" + "roomLength": { + "type": "number" }, - "confirmationToken": { - "type": "string" + "width": { + "type": "number" }, - "role": { - "type": "string" + "height": { + "type": "number" }, - "username": { - "type": "string" + "mirror": { + "type": "boolean" }, - "confirmed": { + "danceBar": { "type": "boolean" }, - "blocked": { + "accomodation": { "type": "boolean" }, - "accepted": { + "technicalStaff": { "type": "boolean" }, - "firstname": { - "type": "string" + "floor": { + "type": "string", + "enum": [ + "plancherDanse", + "parquetTraditionnel", + "other", + "todefine" + ] }, - "lastname": { + "otherFloor": { "type": "string" }, - "structureName": { + "about": { "type": "string" }, - "socialReason": { + "details": { "type": "string" }, "address": { "type": "string" }, - "zipCode": { - "type": "string" - }, - "city": { - "type": "string" - }, - "country": { - "type": "string" - }, - "siret": { - "type": "string" - }, - "ape": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "license": { - "type": "string" + "files": { + "type": "array", + "items": { + "type": "string" + } }, - "website": { - "type": "string" + "images": { + "type": "array", + "items": { + "type": "string" + } }, - "legalRepresentative": { + "users_permissions_user": { "type": "string" }, - "statusRepresentative": { - "type": "string" + "disponibilities": { + "type": "array", + "items": { + "type": "string" + } }, - "insuranceNumber": { + "scheduleDetails": { "type": "string" }, - "insuranceName": { + "filledUntil": { "type": "string" }, - "choreographer": { - "type": "string" + "published": { + "type": "boolean" }, - "espaces": { + "bookings": { "type": "array", "items": { "type": "string" } }, - "type": { + "country": { + "type": "string" + }, + "external_id": { + "type": "integer" + }, + "danceCarpet": { "type": "string", "enum": [ - "company", - "place" + "true", + "false", + "possible" ] }, - "external_id": { - "type": "integer" + "slug": { + "type": "string" }, - "companyDispositifs": { - "type": "array", - "items": { - "type": "string" - } + "city": { + "type": "string" }, - "placeDispositifs": { - "type": "array", - "items": { - "type": "string" - } + "latitude": { + "type": "number" }, - "campaigns": { - "type": "array", - "items": { - "type": "string" - } + "longitude": { + "type": "number" }, - "companyApplications": { + "deleted": { + "type": "boolean" + }, + "applications": { "type": "array", "items": { "type": "string" @@ -1216,10 +1210,10 @@ "campaign": { "type": "string" }, - "place": { + "company": { "type": "string" }, - "company": { + "espace": { "type": "string" }, "published_at": { diff --git a/back/api/application/models/application.settings.json b/back/api/application/models/application.settings.json index 9b792e3d..e31dd672 100644 --- a/back/api/application/models/application.settings.json +++ b/back/api/application/models/application.settings.json @@ -62,14 +62,14 @@ "model": "campaign", "via": "applications" }, - "place": { - "plugin": "users-permissions", - "model": "user" - }, "company": { "plugin": "users-permissions", "model": "user", "via": "companyApplications" + }, + "espace": { + "model": "espace", + "via": "applications" } } } diff --git a/back/api/booking/documentation/1.0.0/booking.json b/back/api/booking/documentation/1.0.0/booking.json index 8f7a52fe..742de7fd 100644 --- a/back/api/booking/documentation/1.0.0/booking.json +++ b/back/api/booking/documentation/1.0.0/booking.json @@ -978,6 +978,12 @@ "deleted": { "type": "boolean" }, + "applications": { + "type": "array", + "items": { + "type": "string" + } + }, "created_by": { "type": "string" }, diff --git a/back/api/campaign/documentation/1.0.0/campaign.json b/back/api/campaign/documentation/1.0.0/campaign.json index 59fc419e..09de7479 100644 --- a/back/api/campaign/documentation/1.0.0/campaign.json +++ b/back/api/campaign/documentation/1.0.0/campaign.json @@ -890,10 +890,10 @@ "campaign": { "type": "string" }, - "place": { + "company": { "type": "string" }, - "company": { + "espace": { "type": "string" }, "published_at": { diff --git a/back/api/city/documentation/1.0.0/city.json b/back/api/city/documentation/1.0.0/city.json index 1661a213..1d880f17 100644 --- a/back/api/city/documentation/1.0.0/city.json +++ b/back/api/city/documentation/1.0.0/city.json @@ -663,6 +663,12 @@ "deleted": { "type": "boolean" }, + "applications": { + "type": "array", + "items": { + "type": "string" + } + }, "created_by": { "type": "string" }, diff --git a/back/api/disponibility/documentation/1.0.0/disponibility.json b/back/api/disponibility/documentation/1.0.0/disponibility.json index afa977f8..15930958 100644 --- a/back/api/disponibility/documentation/1.0.0/disponibility.json +++ b/back/api/disponibility/documentation/1.0.0/disponibility.json @@ -747,6 +747,12 @@ "deleted": { "type": "boolean" }, + "applications": { + "type": "array", + "items": { + "type": "string" + } + }, "created_by": { "type": "string" }, @@ -1080,10 +1086,10 @@ "campaign": { "type": "string" }, - "place": { + "company": { "type": "string" }, - "company": { + "espace": { "type": "string" }, "published_at": { diff --git a/back/api/espace/documentation/1.0.0/espace.json b/back/api/espace/documentation/1.0.0/espace.json index ef6ba9c0..19bea0d2 100644 --- a/back/api/espace/documentation/1.0.0/espace.json +++ b/back/api/espace/documentation/1.0.0/espace.json @@ -1152,6 +1152,76 @@ "deleted": { "type": "boolean", "default": false + }, + "applications": { + "type": "array", + "items": { + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string" + }, + "disponibility": { + "type": "string" + }, + "creation_dancers": { + "type": "integer" + }, + "creation_title": { + "type": "string" + }, + "creation_file": { + "type": "array", + "items": { + "type": "string" + } + }, + "creation_summary": { + "type": "string" + }, + "creation_partnerships": { + "type": "string" + }, + "creation_techical_requirements": { + "type": "string" + }, + "creation_accomodation": { + "type": "boolean" + }, + "eligible": { + "type": "boolean" + }, + "already_supported": { + "type": "boolean" + }, + "cv": { + "type": "string" + }, + "references": { + "type": "object" + }, + "campaign": { + "type": "string" + }, + "company": { + "type": "string" + }, + "espace": { + "type": "string" + }, + "published_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "updated_by": { + "type": "string" + } + } + } } } }, @@ -1276,6 +1346,12 @@ "type": "boolean", "default": false }, + "applications": { + "type": "array", + "items": { + "type": "string" + } + }, "created_by": { "type": "string" }, diff --git a/back/api/espace/models/espace.settings.json b/back/api/espace/models/espace.settings.json index b0286670..3e2eed2f 100644 --- a/back/api/espace/models/espace.settings.json +++ b/back/api/espace/models/espace.settings.json @@ -150,6 +150,10 @@ "deleted": { "type": "boolean", "default": false + }, + "applications": { + "via": "espace", + "collection": "application" } } } 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 2c29f6cb..614c19d2 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": "02/21/2024 10:53:19 AM" + "x-generation-date": "02/21/2024 1:33:15 PM" }, "x-strapi-config": { "path": "/documentation", @@ -10942,7 +10942,7 @@ } } }, - "place": { + "company": { "required": [ "id", "email", @@ -11096,147 +11096,141 @@ } } }, - "company": { + "espace": { "required": [ "id", - "email", - "username", - "firstname", - "lastname", - "structureName", + "name", + "surface", + "roomLength", + "width", + "height", + "mirror", + "danceBar", + "accomodation", + "technicalStaff", + "floor", "address", - "zipCode", - "city", "country", - "siret", - "ape", - "phone", - "license", - "type" + "latitude" ], "properties": { "id": { "type": "string" }, - "email": { - "type": "string" - }, - "provider": { + "name": { "type": "string" }, - "password": { - "type": "string" + "surface": { + "type": "number" }, - "resetPasswordToken": { - "type": "string" + "roomLength": { + "type": "number" }, - "confirmationToken": { - "type": "string" + "width": { + "type": "number" }, - "role": { - "type": "string" + "height": { + "type": "number" }, - "username": { - "type": "string" + "mirror": { + "type": "boolean" }, - "confirmed": { + "danceBar": { "type": "boolean" }, - "blocked": { + "accomodation": { "type": "boolean" }, - "accepted": { + "technicalStaff": { "type": "boolean" }, - "firstname": { - "type": "string" + "floor": { + "type": "string", + "enum": [ + "plancherDanse", + "parquetTraditionnel", + "other", + "todefine" + ] }, - "lastname": { + "otherFloor": { "type": "string" }, - "structureName": { + "about": { "type": "string" }, - "socialReason": { + "details": { "type": "string" }, "address": { "type": "string" }, - "zipCode": { - "type": "string" - }, - "city": { - "type": "string" - }, - "country": { - "type": "string" - }, - "siret": { - "type": "string" - }, - "ape": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "license": { - "type": "string" + "files": { + "type": "array", + "items": { + "type": "string" + } }, - "website": { - "type": "string" + "images": { + "type": "array", + "items": { + "type": "string" + } }, - "legalRepresentative": { + "users_permissions_user": { "type": "string" }, - "statusRepresentative": { - "type": "string" + "disponibilities": { + "type": "array", + "items": { + "type": "string" + } }, - "insuranceNumber": { + "scheduleDetails": { "type": "string" }, - "insuranceName": { + "filledUntil": { "type": "string" }, - "choreographer": { - "type": "string" + "published": { + "type": "boolean" }, - "espaces": { + "bookings": { "type": "array", "items": { "type": "string" } }, - "type": { + "country": { + "type": "string" + }, + "external_id": { + "type": "integer" + }, + "danceCarpet": { "type": "string", "enum": [ - "company", - "place" + "true", + "false", + "possible" ] }, - "external_id": { - "type": "integer" + "slug": { + "type": "string" }, - "companyDispositifs": { - "type": "array", - "items": { - "type": "string" - } + "city": { + "type": "string" }, - "placeDispositifs": { - "type": "array", - "items": { - "type": "string" - } + "latitude": { + "type": "number" }, - "campaigns": { - "type": "array", - "items": { - "type": "string" - } + "longitude": { + "type": "number" }, - "companyApplications": { + "deleted": { + "type": "boolean" + }, + "applications": { "type": "array", "items": { "type": "string" @@ -11294,10 +11288,10 @@ "campaign": { "type": "string" }, - "place": { + "company": { "type": "string" }, - "company": { + "espace": { "type": "string" }, "published_at": { @@ -11779,10 +11773,10 @@ "campaign": { "type": "string" }, - "place": { + "company": { "type": "string" }, - "company": { + "espace": { "type": "string" }, "published_at": { @@ -12053,6 +12047,12 @@ "deleted": { "type": "boolean" }, + "applications": { + "type": "array", + "items": { + "type": "string" + } + }, "created_by": { "type": "string" }, @@ -12313,6 +12313,12 @@ "deleted": { "type": "boolean" }, + "applications": { + "type": "array", + "items": { + "type": "string" + } + }, "created_by": { "type": "string" }, @@ -12646,10 +12652,10 @@ "campaign": { "type": "string" }, - "place": { + "company": { "type": "string" }, - "company": { + "espace": { "type": "string" }, "published_at": { @@ -13492,6 +13498,12 @@ "type": "boolean", "default": false }, + "applications": { + "type": "array", + "items": { + "type": "string" + } + }, "created_by": { "type": "string" }, diff --git a/back/extensions/users-permissions/documentation/1.0.0/users-permissions-User.json b/back/extensions/users-permissions/documentation/1.0.0/users-permissions-User.json index ce129434..2aeeb787 100644 --- a/back/extensions/users-permissions/documentation/1.0.0/users-permissions-User.json +++ b/back/extensions/users-permissions/documentation/1.0.0/users-permissions-User.json @@ -1457,6 +1457,12 @@ "deleted": { "type": "boolean" }, + "applications": { + "type": "array", + "items": { + "type": "string" + } + }, "created_by": { "type": "string" }, @@ -1721,10 +1727,10 @@ "campaign": { "type": "string" }, - "place": { + "company": { "type": "string" }, - "company": { + "espace": { "type": "string" }, "published_at": { diff --git a/web/components/Account/Application/Company/ApplicationCompanyList.tsx b/web/components/Account/Application/Company/ApplicationCompanyList.tsx index dfee32c1..27657592 100644 --- a/web/components/Account/Application/Company/ApplicationCompanyList.tsx +++ b/web/components/Account/Application/Company/ApplicationCompanyList.tsx @@ -39,6 +39,8 @@ const ApplicationCompanyList = ({ applications = [] }: Props) => { setList(list.reverse()) } + console.log(applications) + return ( { {application?.id} - {application?.place?.structureName} + + { + //@ts-expect-error + application.disponibility.espace.users_permissions_user + .structureName + } + {/* @ts-expect-error */} diff --git a/web/components/Account/Application/Place/ApplicationPlaceList.tsx b/web/components/Account/Application/Place/ApplicationPlaceList.tsx index 86b0654b..1804f77b 100644 --- a/web/components/Account/Application/Place/ApplicationPlaceList.tsx +++ b/web/components/Account/Application/Place/ApplicationPlaceList.tsx @@ -11,9 +11,9 @@ import { useTranslation } from 'next-i18next' import { Application } from '~typings/api' import Chevron from 'public/assets/img/chevron-down.svg' import Cell from '~components/Account/Booking/Cell' -import ApplicationCompanyListItem from '~components/Account/Application/Company/ApplicationCompanyListItem' import useCampaignContext from '~components/Campaign/useCampaignContext' -import ApplicationCompanyHelper from '~components/Account/Application/Company/ApplicationCompanyHelper' +import ApplicationPlaceListItem from '~components/Account/Application/Place/ApplicationPlaceListItem' +import ApplicationSelector from '~components/Account/Application/Place/ApplicationSelector' interface Props { applications: Application[] @@ -58,18 +58,15 @@ const ApplicationPlaceList = ({ applications = [] }: Props) => { {t('place.title', { title: currentCampaign?.title })} - + + {t('place.table.head.number')} - - - {t('place.table.head.place')} - - - - {t('place.table.head.space')} - { cursor="pointer" onClick={sortByDate} > - {t('place.table.head.slot')} + {t('place.table.head.artist')} @@ -87,20 +84,24 @@ const ApplicationPlaceList = ({ applications = [] }: Props) => { - {t('place.table.head.creation')} + {t('place.table.head.email')} + {t('place.table.head.creation')} - + {currentCampaign?.mode === 'preselections' && ( + + + + )} {list.map((application) => ( - ))} - ) } diff --git a/web/components/Account/Application/Place/ApplicationPlaceListItem.tsx b/web/components/Account/Application/Place/ApplicationPlaceListItem.tsx index b9d9b3a6..6779caf2 100644 --- a/web/components/Account/Application/Place/ApplicationPlaceListItem.tsx +++ b/web/components/Account/Application/Place/ApplicationPlaceListItem.tsx @@ -8,12 +8,15 @@ import ConfirmButton from '~components/Account/Application/ConfirmButton' import { client } from '~api/client-api' import useToast from '~hooks/useToast' import { useQueryClient } from 'react-query' +import Link from '~components/Link' +import useCampaignContext from '~components/Campaign/useCampaignContext' interface Props { application: Application } const ApplicationPlaceListItem = ({ application }: Props) => { + const { currentCampaign } = useCampaignContext() const { errorToast, successToast } = useToast() const { t } = useTranslation('application') const queryClient = useQueryClient() @@ -34,41 +37,38 @@ const ApplicationPlaceListItem = ({ application }: Props) => { {application?.id} - {application?.place?.structureName} + {`${application?.company?.structureName} (${application.company.firstname} ${application.company.lastname})`} - {/* @ts-expect-error */} - {application?.disponibility?.espace?.name} - - - {`${format(application?.disponibility.start, 'dd/MM')} → ${format( - application?.disponibility.end, - 'dd/MM', - )}`} + + {application?.company?.email} + {application?.creation_title} - - - - - + + + + )} ) } diff --git a/web/components/Account/Application/Place/ApplicationSelector.tsx b/web/components/Account/Application/Place/ApplicationSelector.tsx new file mode 100644 index 00000000..405a768f --- /dev/null +++ b/web/components/Account/Application/Place/ApplicationSelector.tsx @@ -0,0 +1,19 @@ +import { HStack, Select } from '@chakra-ui/react' +import { useMyPlaces } from '~hooks/useMyPlaces' + +const ApplicationSelector = () => { + const { data: places, isLoading } = useMyPlaces() + if (isLoading || !places) return null + + return ( + + + + ) +} + +export default ApplicationSelector diff --git a/web/components/Campaign/CampaignTag.tsx b/web/components/Campaign/CampaignTag.tsx index a2ed7296..036adac8 100644 --- a/web/components/Campaign/CampaignTag.tsx +++ b/web/components/Campaign/CampaignTag.tsx @@ -9,13 +9,15 @@ const CampaignTag = ({ isGrid, isCampaignTab, disponibilitiesIds, + hasCampaignDispo, }: { isGrid?: boolean isCampaignTab?: boolean disponibilitiesIds?: string[] + hasCampaignDispo?: boolean }) => { const { applications } = useCurrentUser() - const { currentCampaign, isCampaignPlace } = useCampaignContext() + const { currentCampaign } = useCampaignContext() const { t } = useTranslation('common') if ( @@ -42,7 +44,7 @@ const CampaignTag = ({ ) } - if (isCampaignPlace && !isCampaignTab) { + if (hasCampaignDispo && !isCampaignTab) { return ( { - console.log(place) const { applications } = useCurrentUser() const { t } = useTranslation('place') const schema = yup.object().shape({ @@ -100,7 +99,7 @@ const ApplicationForm = ({ form: new FormData(), data: { company: user.id, - place: place.users_permissions_user.id, + place: place.id, disponibility: event?.extendedProps?.id, campaign: currentCampaign?.id, }, diff --git a/web/components/Place/PlaceGridCard.tsx b/web/components/Place/PlaceGridCard.tsx index a8ca50f0..ec2307b5 100644 --- a/web/components/Place/PlaceGridCard.tsx +++ b/web/components/Place/PlaceGridCard.tsx @@ -22,7 +22,6 @@ import addWeeks from 'date-fns/addWeeks' import useCampaignContext from '~components/Campaign/useCampaignContext' import { format } from '~utils/date' import CampaignTag from '~components/Campaign/CampaignTag' -import { pl } from 'date-fns/locale' interface Props { place: Espace @@ -49,7 +48,7 @@ const PlaceGridCard = ({ place, searchParams, isCampaignTab }: Props) => { disposInRange || place?.disponibilities, ) - const isCampaignPlace = + const hasCampaignDispo = currentCampaign?.mode === 'applications' && place?.disponibilities?.some( //@ts-expect-error @@ -76,7 +75,10 @@ const PlaceGridCard = ({ place, searchParams, isCampaignTab }: Props) => { d?.id)} + disponibilitiesIds={place?.disponibilities + ?.filter((el) => el?.campaign) + .map((d) => d?.id)} + hasCampaignDispo={hasCampaignDispo} /> { - {isCampaignPlace && ( + {hasCampaignDispo && isCampaignTab && ( <> diff --git a/web/pages/compte/candidatures/index.tsx b/web/pages/compte/candidatures/index.tsx index 2dc59c5a..26c10ccc 100644 --- a/web/pages/compte/candidatures/index.tsx +++ b/web/pages/compte/candidatures/index.tsx @@ -37,7 +37,7 @@ export const getServerSideProps: GetServerSideProps = requireAuth( async ({ locale }) => { return { props: { - ...(await serverSideTranslations(locale, ['account'])), + ...(await serverSideTranslations(locale, ['account', 'application'])), }, } }, diff --git a/web/public/locales/fr/account.json b/web/public/locales/fr/account.json index a3d5e4b2..a3e073fd 100644 --- a/web/public/locales/fr/account.json +++ b/web/public/locales/fr/account.json @@ -7,7 +7,9 @@ "bookings": "Mes réservations", "message": "Messagerie", "createPlace": "Créer un espace", - "places": "Mes espaces" + "places": "Mes espaces", + "applications": "Candidatures", + "my_applications": "Mes candidatures" }, "error": { "emailTaken": "Cet email est déjà utilisé" diff --git a/web/public/locales/fr/application.json b/web/public/locales/fr/application.json index 7bc02a86..1685ffca 100644 --- a/web/public/locales/fr/application.json +++ b/web/public/locales/fr/application.json @@ -25,7 +25,7 @@ "place": { "title": "Candidatures – Dispositif {{title}}", "table": { - "header": { + "head": { "number": "No.", "artist": "Artiste", "email": "Email",