Skip to content
Snippets Groups Projects
Consents.tsx 9.88 KiB
Newer Older
  • Learn to ignore specific revisions
  • Guilhem CARRON's avatar
    Guilhem CARRON committed
    import React, {
      useCallback,
      useContext,
      useEffect,
      useMemo,
      useState,
    } from 'react'
    import { AgGridReact } from 'ag-grid-react'
    import 'ag-grid-community/dist/styles/ag-grid.css'
    import 'ag-grid-community/dist/styles/ag-theme-alpine-dark.css'
    import TablePagination from '@material-ui/core/TablePagination'
    import {
      ColDef,
      ColGroupDef,
      CsvExportParams,
      GridApi,
      GridReadyEvent,
      RowNode,
      RowSelectedEvent,
    
      ValueFormatterParams,
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
    } from 'ag-grid-community'
    import styles from './consents.module.scss'
    import './agGridOverrides.scss'
    import './muiPaginationOverrides.scss'
    import DowloadModal from './DowloadModal'
    import { ConsentService } from '../../services/consent.service'
    import { UserContextProps, UserContext } from '../../hooks/userContext'
    import { IConsent } from '../../models/consent.model'
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
    import { getAxiosXSRFHeader } from '../../axios.config'
    
    import { DateTime } from 'luxon'
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
    
    const Consents: React.FC = () => {
      const [gridApi, setGridApi] = useState<GridApi | null>(null)
      const [search, setSearch] = useState<string>('')
      const [selectedNodes, setSelectedNodes] = useState<RowNode[]>([])
      const [isShowingSelection, setIsShowingSelection] = useState<boolean>(false)
      const [openDowloadModal, setOpenDowloadModal] = useState<boolean>(false)
      const [consents, setConsents] = useState<IConsent[]>([])
      const [page, setPage] = useState<number>(0)
      const [rowsPerPage, setRowsPerPage] = useState<number>(50)
      const [totalRows, setTotalRows] = useState<number>(50)
      const { user }: Partial<UserContextProps> = useContext(UserContext)
      const consentService = useMemo(() => {
        return new ConsentService()
      }, [])
    
      const toggleOpenModal = useCallback(() => {
        setOpenDowloadModal((prev) => !prev)
      }, [])
    
      const defaultColDef = useMemo(
        () => ({
          sortable: true,
          resizable: true,
        }),
        []
      )
    
    
      const dateFormatter = (data: ValueFormatterParams): string => {
        return (data.value as DateTime).toLocaleString()
      }
    
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
      const [columnDefs] = useState<(ColDef | ColGroupDef)[] | null>([
        {
          field: 'ID',
          hide: true,
        },
        {
          field: 'pointID',
          headerName: 'N° PDL',
    
          initialWidth: 180,
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
          filter: true,
          checkboxSelection: true,
        },
    
        {
          field: 'lastname',
          headerName: 'Nom',
          initialWidth: 180,
          filter: true,
          cellStyle: { textTransform: 'uppercase' },
        },
        {
          field: 'firstname',
          headerName: 'Prénom',
          initialWidth: 180,
          filter: true,
          cellStyle: { textTransform: 'capitalize' },
        },
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
        {
          field: 'address',
          headerName: 'Adresse',
          initialWidth: 300,
          filter: true,
          flex: 1,
        },
    
        {
          field: 'postalCode',
          headerName: 'CP',
    
          initialWidth: 80,
    
          filter: true,
        },
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
        {
          field: 'city',
          headerName: 'Ville',
        },
    
        {
          field: 'safetyOnBoarding',
          headerName: 'Secours',
    
          initialWidth: 100,
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
        {
          field: 'startDate',
    
          valueFormatter: dateFormatter,
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
          headerName: 'Début du consentement',
    
          initialWidth: 150,
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
          filter: true,
    
          sort: 'desc',
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
        },
        {
          field: 'endDate',
    
          valueFormatter: dateFormatter,
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
          headerName: 'Fin du consentement',
    
          initialWidth: 150,
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
          filter: true,
        },
      ])
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
      const handleChangePage = useCallback(
        (
          _event: React.MouseEvent<HTMLButtonElement, MouseEvent> | null,
          newPage: number
        ) => {
          setPage(newPage)
        },
        []
      )
      const handleChangeRowsPerPage = useCallback((event: any) => {
        setRowsPerPage(event.target.value)
        setPage(0)
      }, [])
    
      const checkSelectedNodes = useCallback(() => {
        if (gridApi) {
          const newNodes = gridApi.getRenderedNodes()
          const idsToCheck: string[] = selectedNodes
            .filter((node: RowNode) => node.isSelected)
            .map((node: RowNode) => node.data.ID)
    
          newNodes.forEach((node: RowNode) => {
            if (idsToCheck.includes(node.data.ID))
              node.setSelected(true, false, true)
          })
        }
      }, [gridApi, selectedNodes])
    
      const handleSearchChange = useCallback(
        async (newSearch: string) => {
          setSearch(newSearch)
          if (user) {
    
            const consentPagination = await consentService.searchConsents(
              newSearch,
              rowsPerPage,
              page,
              getAxiosXSRFHeader(user.xsrftoken)
            )
            if (consentPagination) {
              setConsents(consentPagination.rows)
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
              checkSelectedNodes()
    
              setTotalRows(consentPagination.totalRows)
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
            }
          }
        },
        [user, consentService, rowsPerPage, page, checkSelectedNodes]
      )
    
      const resetSelection = useCallback(() => {
        if (gridApi) {
          setIsShowingSelection(false)
          gridApi.setRowData(consents)
          gridApi.deselectAll()
          setSelectedNodes([])
        }
      }, [gridApi, consents])
    
      const onRowSelected = useCallback(
        (event: RowSelectedEvent) => {
          if (event.node.isSelected()) {
            const index = selectedNodes.findIndex(
              (node) => node.data.ID === event.node.data.ID
            )
            if (index === -1) {
              setSelectedNodes((prev) => [...prev, event.node])
            }
          } else {
            setSelectedNodes((prev) =>
              prev.filter((node) => {
                return node.data.ID != event.node.data.ID
              })
            )
          }
        },
        [selectedNodes]
      )
    
      const continueSelection = useCallback(() => {
        if (gridApi) {
          setIsShowingSelection(false)
          gridApi?.setRowData(consents)
          const newNodes = gridApi.getRenderedNodes()
          // We have to select nodes that have already been selected since we cannot pass a Node array to init AgGrid
          const idsToCheck: string[] = selectedNodes
            .filter((node: RowNode) => node.isSelected)
            .map((node: RowNode) => node.data.ID)
    
          newNodes.forEach((node: RowNode) => {
            if (idsToCheck.includes(node.data.ID)) node.setSelected(true)
          })
        }
      }, [gridApi, consents, selectedNodes])
    
      const showCurrentSelection = useCallback(() => {
        setIsShowingSelection(true)
        const dataFromNode = selectedNodes.map((item: RowNode) => item.data)
        selectedNodes && gridApi?.setRowData(dataFromNode)
        gridApi?.selectAll()
      }, [gridApi, selectedNodes])
    
      const exportData = useCallback(() => {
        //You can change default column separator
        const params: CsvExportParams = {
          columnSeparator: ',',
        }
        gridApi?.exportDataAsCsv(params)
        setOpenDowloadModal(false)
        resetSelection()
      }, [gridApi, resetSelection])
    
      const onGridReady = ({ api }: GridReadyEvent) => {
        //Grid init method
        setGridApi(api)
        api.sizeColumnsToFit()
      }
    
      useEffect(() => {
        function handleResize() {
          gridApi?.sizeColumnsToFit()
        }
        handleResize()
        window.addEventListener('resize', handleResize)
        return () => {
          window.removeEventListener('resize', handleResize)
        }
      }, [gridApi])
    
    
      /** Trigger search when page loads or when admin changes pagination */
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
      useEffect(() => {
    
        handleSearchChange(search)
        // /!\ Do not change dependencies or effect will not trigger when pagination changes
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
        // eslint-disable-next-line react-hooks/exhaustive-deps
    
      }, [rowsPerPage, page])
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
    
      return (
        <>
    
          <div className="header">
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
            <p className="title pagetitle">Gestion des consentements Enedis</p>
          </div>
    
          <div className={styles.content}>
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
            <div className={styles.searchField}>
              <div className={styles.inputGroup}>
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
                <label htmlFor="search">Recherche</label>
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
                <input
                  value={search}
                  name="search"
    
                  type="number"
                  placeholder="N°PDL (14 chiffres)"
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
                  onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                    handleSearchChange(e.target.value)
                  }
    
                  disabled={isShowingSelection}
    
                  autoComplete="off"
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
                ></input>
              </div>
            </div>
            <div
              className="ag-theme-alpine-dark"
    
              style={{ width: '100%', height: '75vh' }}
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
            >
              <AgGridReact
                onGridReady={onGridReady}
                defaultColDef={defaultColDef}
                rowHeight={35}
                rowData={consents}
                columnDefs={columnDefs}
                animateRows={true}
                rowSelection="multiple"
                allowDragFromColumnsToolPanel={false}
                onRowSelected={onRowSelected}
                sortingOrder={['asc', 'desc']}
                rowMultiSelectWithClick={true}
                pagination={false}
                suppressCellFocus={true}
              ></AgGridReact>
    
              {!isShowingSelection && (
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
                <TablePagination
                  labelRowsPerPage="Consentements par page"
                  component="div"
                  count={totalRows}
                  page={page}
                  onPageChange={handleChangePage}
                  rowsPerPage={rowsPerPage}
                  onRowsPerPageChange={handleChangeRowsPerPage}
                  rowsPerPageOptions={[10, 25, 50, 100]}
                />
              )}
            </div>
            {openDowloadModal && (
              <DowloadModal
                toggleOpenModal={toggleOpenModal}
                exportData={exportData}
              />
            )}
          </div>
    
          <div className={styles.footerButtons}>
            <button
              className="btnDelete"
              onClick={isShowingSelection ? continueSelection : resetSelection}
              disabled={
                !isShowingSelection && selectedNodes && selectedNodes.length === 0
              }
            >
              {isShowingSelection
                ? 'Continuer ma sélection'
                : 'Tout déselectionner'}
            </button>
            <button
              className={styles.btnSelection + ' btnValid'}
              onClick={!isShowingSelection ? showCurrentSelection : toggleOpenModal}
              disabled={selectedNodes && selectedNodes.length <= 0}
            >
              {!isShowingSelection ? 'Voir mes sélections' : 'Télécharger'}
              <span>{selectedNodes?.length}</span>
            </button>
          </div>
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
        </>
      )
    }
    export default Consents