diff --git a/src/components/Settings/Settings.tsx b/src/components/Settings/Settings.tsx index 2b225d8b04ba8658d34b564514a7758abaf584c7..70d4ac14ff287b1b3f8c5561654ec781839b1974 100644 --- a/src/components/Settings/Settings.tsx +++ b/src/components/Settings/Settings.tsx @@ -1,15 +1,36 @@ +import { DateTime } from 'luxon' import React, { useCallback, useContext, useEffect, useState } from 'react' import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' import { getAxiosXSRFHeader } from '../../axios.config' import { CheckboxType } from '../../enum/checkboxType.enum' import { UserContext, UserContextProps } from '../../hooks/userContext' -import { ICustomPopup } from '../../models/cutomPopup.model' +import { ICustomPopup, PopupDuration } from '../../models/cutomPopup.model' +import { + durationEnum, + durationType, + Option, +} from '../../models/durationOptios.model' import { IPartnersInfo } from '../../models/partnersInfo.model' import { CustomPopupService } from '../../services/customPopup.service' import { PartnersInfoService } from '../../services/partnersInfo.service' import Loader from '../Loader/Loader' import './settings.scss' +const OPTIONS: Array<Option> = [ + { + value: durationEnum.hours, + label: 'Heures', + }, + { + value: durationEnum.days, + label: 'Jours', + }, + { + value: durationEnum.infinite, + label: 'Indéterminée', + }, +] + const Settings: React.FC = () => { const [refreshData, setRefreshData] = useState(false) const [isLoading, setIsLoading] = useState(false) @@ -19,11 +40,18 @@ const Settings: React.FC = () => { egl_failure: false, notification_activated: false, }) + const [previousEndDate, setPreviousEndDate] = useState<string>() const [customPopup, setCustomPopup] = useState<ICustomPopup>({ popupEnabled: false, title: '', description: '', + endDate: DateTime.local().toISO(), + }) + const [popupDuration, setPopupDuration] = useState<PopupDuration>({ + type: 'days', + duration: 0, }) + const { user }: Partial<UserContextProps> = useContext(UserContext) const isPartnerNotificationOn = () => @@ -31,6 +59,12 @@ const Settings: React.FC = () => { partnersInfo.egl_failure || partnersInfo.grdf_failure + /** + * Only one type of popup can be enabled + */ + const isPageValid = () => + !(isPartnerNotificationOn() && customPopup.popupEnabled) + const handleCheckboxChange = (value: boolean, type: CheckboxType): void => { switch (type) { case CheckboxType.GRDF: @@ -90,7 +124,7 @@ const Settings: React.FC = () => { resetFields() setIsLoading(true) - async function getSettings() { + async function loadSettings() { if (user) { const partnersInfoService = new PartnersInfoService() const customPopupService = new CustomPopupService() @@ -106,12 +140,13 @@ const Settings: React.FC = () => { setCustomPopup({ ...customPopupData, }) + setPreviousEndDate(customPopupData.endDate || undefined) } } setIsLoading(false) } if (subscribed) { - getSettings() + loadSettings() } return () => { subscribed = false @@ -137,9 +172,59 @@ const Settings: React.FC = () => { customPopup, getAxiosXSRFHeader(user.xsrftoken) ) + setPreviousEndDate(customPopup.endDate) } } + const handleSelectChange = (event: React.ChangeEvent<HTMLSelectElement>) => { + setPopupDuration((prev) => ({ + ...prev, + type: event.target.value as durationType, + })) + } + + /** + * Handles duration change + */ + useEffect(() => { + const now = DateTime.local() + let newDate: DateTime + if (popupDuration.type !== durationEnum.infinite) { + newDate = now.plus({ + [popupDuration.type]: popupDuration.duration, + }) + } else if (popupDuration.type === 'infinite') { + newDate = now.plus({ + years: 1, + }) + } + setCustomPopup((prev) => ({ + ...prev, + endDate: newDate.toISO(), + })) + }, [popupDuration]) + + const isPopupOutdated = (date: string) => + DateTime.local() >= DateTime.fromISO(date) + + /** + * Returns "Popup expirée" OR "Temps restant : ..." + */ + const getRemainingDuration = (date: string) => { + if (isPopupOutdated(date)) { + return <p className="endDate">Popup expirée</p> + } + return ( + <p className="endDate"> + Temps d'activation restant :<br /> + {DateTime.fromISO(date) + .diffNow(['days', 'hours', 'minutes', 'seconds']) + .set({ second: 0 }) + .toHuman()} + </p> + ) + } + return ( <> <div className="header"> @@ -201,22 +286,28 @@ const Settings: React.FC = () => { </div> </div> - <div className="customInfo"> + <div className="customPopup"> <h2 className="title">Affichage de pop-up personnalisée</h2> - <div className="switch_div"> - <span>Pop-up active</span> - <input - type="checkbox" - id="switch_popup" - checked={customPopup.popupEnabled} - onChange={(event) => { - handleCheckboxChange( - event.currentTarget.checked, - CheckboxType.CUSTOM - ) - }} - /> - <label htmlFor="switch_popup"></label> + <div className="currentPopup"> + <div className="switch_div"> + <span>Pop-up active</span> + <input + type="checkbox" + id="switch_popup" + checked={customPopup.popupEnabled} + onChange={(event) => { + handleCheckboxChange( + event.currentTarget.checked, + CheckboxType.CUSTOM + ) + }} + /> + <label htmlFor="switch_popup"></label> + </div> + + {customPopup.popupEnabled && + previousEndDate && + getRemainingDuration(previousEndDate)} </div> {customPopup.popupEnabled && ( <> @@ -226,6 +317,7 @@ const Settings: React.FC = () => { type="text" name="title" id="title" + min={1} placeholder="Titre" value={customPopup.title} onChange={(event) => handlePopupChange(event, 'title')} @@ -248,6 +340,38 @@ const Settings: React.FC = () => { {customPopup.description.length} / 250 </p> </div> + <div className="popupEndDate"> + <label htmlFor="title">Nouvelle Durée</label> + <div className="durationInput"> + <select + value={popupDuration.type} + onChange={(event) => handleSelectChange(event)} + > + {OPTIONS.map((option) => ( + <option + key={option.value} + value={option.value} + selected={popupDuration.type === option.value} + > + {option.label} + </option> + ))} + </select> + {popupDuration.type !== 'infinite' && ( + <input + type="number" + min="0" + value={popupDuration.duration} + onChange={(e) => + setPopupDuration((prev) => ({ + ...prev, + duration: Number(e.target.value), + })) + } + /> + )} + </div> + </div> </> )} </div> @@ -256,7 +380,12 @@ const Settings: React.FC = () => { <button className="btnCancel" onClick={handleCancel}> Annuler </button> - <button className="btnValid" onClick={handleSave}> + <button + className="btnValid" + onClick={handleSave} + disabled={!isPageValid()} + title="Un seul type de popup peut être activé" + > Sauvegarder </button> </div> diff --git a/src/components/Settings/settings.scss b/src/components/Settings/settings.scss index a7b0bcef047761e1bee7711b32ce246391e130d3..72ba702ca501f19d65516ec18dee7756c155fd79 100644 --- a/src/components/Settings/settings.scss +++ b/src/components/Settings/settings.scss @@ -2,7 +2,7 @@ .settings { .partnersInfo, - .customInfo { + .customPopup { h2.title { margin: 1rem 0; } @@ -19,12 +19,28 @@ } } + .customPopup { + .currentPopup { + display: flex; + margin-bottom: 1rem; + .switch_div { + padding-top: 0; + } + + p.endDate { + color: $gold-dark; + font-weight: bold; + } + } + } + .popupTitle { margin-bottom: 1.5rem; } .popupTitle, - .popupDescription { + .popupDescription, + .popupEndDate { display: flex; flex-direction: column; gap: 0.5rem; @@ -56,6 +72,33 @@ } } + .popupEndDate { + .durationInput { + display: flex; + gap: 1.5rem; + max-height: 36px; + + input, + select { + background: #383941; + border: 1px solid $text-chart; + border-radius: 2px; + } + + input { + max-width: 100px; + } + + select { + max-width: 180px; + padding: 0.5rem 1rem; + option { + background-color: $grey-light; + } + } + } + } + .buttons { position: fixed; bottom: 1rem; @@ -121,11 +164,4 @@ width: 34px; } } - - .customInfo { - .switch_div { - padding-top: 0; - margin-bottom: 1rem; - } - } } diff --git a/src/models/cutomPopup.model.ts b/src/models/cutomPopup.model.ts index 9af8abd26f9f4609fd74d77fefc1692368dc365b..d14166d32faeada7321363015da2e3229fc906ac 100644 --- a/src/models/cutomPopup.model.ts +++ b/src/models/cutomPopup.model.ts @@ -1,5 +1,13 @@ +import { durationType } from './durationOptios.model' + export interface ICustomPopup { description: string popupEnabled: boolean + endDate: string title: string } + +export interface PopupDuration { + type: durationType + duration: number +} diff --git a/src/models/durationOptios.model.ts b/src/models/durationOptios.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..8e71e9a2601000d9a6867cc21ffe889a31a8583b --- /dev/null +++ b/src/models/durationOptios.model.ts @@ -0,0 +1,12 @@ +export type durationType = 'hours' | 'days' | 'infinite' + +export enum durationEnum { + hours = 'hours', + days = 'days', + infinite = 'infinite', +} + +export interface Option { + value: durationType + label: string +} diff --git a/src/styles/config/_colors.scss b/src/styles/config/_colors.scss index 2abf8f66da66f296c0319d29f77553bbd0a1c922..26ffcd3eee0cf29c7e2166e854e27d559780f7be 100644 --- a/src/styles/config/_colors.scss +++ b/src/styles/config/_colors.scss @@ -5,6 +5,7 @@ $dark-background: radial-gradient( #1b1c22 100% ); $gold: #e3b82a; +$gold-dark: #e2a70d; $btn-gold: #f1c017; $dark-light: #1b1c22; $grey-dark: #25262b;