Skip to content

Commit

Permalink
Merge branch '4001-datatable-use-datagrid' into 4001-datatable-use-da…
Browse files Browse the repository at this point in the history
…tagrid-input-disabled
  • Loading branch information
mergify[bot] authored Jan 2, 2025
2 parents 37225d6 + a064006 commit 017964d
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useIsPrintRoute } from 'client/hooks/useIsRoute'
import { ButtonProps, useButtonClassName } from 'client/components/Buttons/Button'
import Icon from 'client/components/Icon'

import { useFilename } from './hooks/useFilename'
import * as Utils from './utils'

type Props = Pick<ButtonProps, 'size'> & {
Expand All @@ -15,14 +16,15 @@ type Props = Pick<ButtonProps, 'size'> & {
}

const ButtonGridExport: React.FC<Props> = (props) => {
const { disabled, filename, gridRef, size } = props
const { disabled, filename: filenameProp, gridRef, size } = props

const [data, setData] = useState<Array<object>>([])

const { print } = useIsPrintRoute()
const isLocked = useIsDataLocked()

const className = useButtonClassName({ disabled: !isLocked || disabled, iconName: 'hit-down', label: 'CSV', size })
const filename = useFilename(filenameProp)

if (print) return null

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useMemo } from 'react'

import { Dates } from 'utils/dates'

import { useCycleRouteParams } from 'client/hooks/useRouteParams'

export const useFilename = (filename: string): string => {
const { assessmentName, cycleName } = useCycleRouteParams()
return useMemo(() => {
const date = Dates.format(new Date(), 'yyyy-MM-dd')
return `${assessmentName}-${cycleName}-${filename}-${date}.csv`
}, [assessmentName, cycleName, filename])
}
91 changes: 78 additions & 13 deletions src/client/components/DataGrid/ButtonGridExport/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Objects } from 'utils/objects'

// Converts one or mutiple white spaces or break lines into one space.
const normalizeString = (string = '') => string.trim().replace(/\s+/g, ' ')

const _getElementText = (element: HTMLElement): string => {
if (typeof element === 'string') return normalizeString(element)

const { children, innerText, style } = element
const { children, innerText } = element
const computedStyle = getComputedStyle(element)

if (style && (style.visibility === 'hidden' || style.display === 'none')) return ''
if (computedStyle && (computedStyle.visibility === 'hidden' || computedStyle.display === 'none')) return ''

if (element.classList.contains('no-csv')) return ''

Expand All @@ -33,27 +36,89 @@ const _getElementText = (element: HTMLElement): string => {
return normalizeString(innerText)
}

type DataRow = Array<string>
const _getCellSpans = (props: {
cell: Element
columnCount: number
rowCount: number
}): { colSpan: number; rowSpan: number } => {
const { cell, columnCount, rowCount } = props
const style = getComputedStyle(cell)

const parseValue = (value: string, type: 'col' | 'row'): number => {
if (value === 'auto') return 1
if (value.startsWith('span')) return parseInt(value.split(' ')[1], 10)
if (value.includes('/')) {
const [start, end] = value.split('/').map((v) => v.trim())
if (start === '1' && end === '-1') {
// Spans the entire row or column
return type === 'col' ? columnCount : rowCount
}
return Math.abs(parseInt(end, 10) - parseInt(start, 10))
}
return parseInt(value, 10)
}

const colSpan = parseValue(style.gridColumn, 'col')
const rowSpan = parseValue(style.gridRow, 'row')

return { colSpan, rowSpan }
}

type DataRow = Array<string | null>
type TableData = Array<DataRow>

export const getDataGridData = (grid: HTMLDivElement): TableData => {
if (!grid) {
return []
}
const data: TableData = []
const cells = grid.querySelectorAll('.data-cell')
let currentRow: DataRow = []
const gridStyle = getComputedStyle(grid)
const columnCount = gridStyle.getPropertyValue('grid-template-columns').split(' ').length
const rowCount = gridStyle.getPropertyValue('grid-template-rows').split(' ').length

cells.forEach((cell) => {
const isLastCol = cell.classList.contains('lastCol')
const data: TableData = Array.from({ length: rowCount }, () => new Array(columnCount).fill(null))

const cellText = _getElementText(cell as HTMLElement)
const cells = Array.from(grid.children)

currentRow.push(cellText)
const findNextAvailablePosition = (): { row: number; col: number } | null => {
for (let row = 0; row < data.length; row += 1) {
for (let col = 0; col < data[row].length; col += 1) {
if (Objects.isNil(data[row][col])) {
return { col, row }
}
}
}
return null
}

if (isLastCol) {
data.push([...currentRow])
currentRow = []
cells.forEach((cell) => {
const nextAvailablePosition = findNextAvailablePosition()
if (Objects.isNil(nextAvailablePosition)) return
const { col, row } = nextAvailablePosition

const isNoticeMessage = cell.classList.contains('table-grid__notice-message-cell')

let cellContent = _getElementText(cell as HTMLElement)
const spaceFreeContent = cellContent.replace(/\s/g, '')
cellContent =
Number.isNaN(Number.parseFloat(spaceFreeContent)) || Number.isNaN(Number(spaceFreeContent))
? cellContent
: spaceFreeContent

const { rowSpan, colSpan } = _getCellSpans({ cell, columnCount, rowCount })

for (let r = row; r < row + rowSpan && r < rowCount; r += 1) {
for (let c = col; c < col + colSpan && c < columnCount; c += 1) {
// Place notice message only in the first cell of the row
if (isNoticeMessage) {
if (r === row && c === col) {
data[r][c] = cellContent
} else {
data[r][c] = ''
}
} else {
data[r][c] = cellContent
}
}
}
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const Title = (props: Props) => {

return (
<div className="odp__section-header">
<ButtonGridExport disabled={year === -1} filename={`FRA${cycleName}-NDP${year}.csv`} gridRef={gridRef} />
<ButtonGridExport disabled={year === -1} filename={`NDP${year}`} gridRef={gridRef} />
<h3 className="subhead">
{t(`nationalDataPoint.${cycleName !== '2020' ? 'nationalClassifications' : 'nationalClasses'}`)}
</h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import classNames from 'classnames'
import { Routes } from 'meta/routes'
import { TooltipId } from 'meta/tooltip'

import { useAssessment, useCycle } from 'client/store/assessment'
import { useOdpReviewSummary } from 'client/store/ui/review/hooks'
import { useCountryIso } from 'client/hooks'
import { useIsPrintRoute } from 'client/hooks/useIsRoute'
import { useCycleRouteParams } from 'client/hooks/useRouteParams'
import { DataCell } from 'client/components/DataGrid'
import ReviewSummaryIndicator from 'client/components/ReviewSummaryIndicator'

Expand All @@ -28,9 +28,8 @@ type Props = {
const OdpHeaderCell: React.FC<Props> = (props) => {
const { className, gridColumn, gridRow, lastCol, odpId, odpYear, sectionName } = props

const assessment = useAssessment()
const { assessmentName, cycleName } = useCycleRouteParams()
const countryIso = useCountryIso()
const cycle = useCycle()

const { print } = useIsPrintRoute()
const { t } = useTranslation()
Expand All @@ -53,21 +52,23 @@ const OdpHeaderCell: React.FC<Props> = (props) => {
header
lastCol={lastCol}
>
<Link
className="link"
data-tooltip-content={t('nationalDataPoint.clickOnNDP')}
data-tooltip-id={TooltipId.info}
to={Routes.OriginalDataPoint.generatePath({
assessmentName: assessment.props.name,
countryIso,
cycleName: cycle.name,
sectionName,
year: odpYear,
})}
>
{odpYear}
<div>
<Link
className="link"
data-tooltip-content={t('nationalDataPoint.clickOnNDP')}
data-tooltip-id={TooltipId.info}
to={Routes.OriginalDataPoint.generatePath({
assessmentName,
countryIso,
cycleName,
sectionName,
year: odpYear,
})}
>
{odpYear}
</Link>
<ReviewSummaryIndicator status={reviewStatus} />
</Link>
</div>
</DataCell>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@
display: flex;
justify-content: end;

&.calculated {
&.readonly,
&.calculated-input {
cursor: default;
font-weight: 600;
}

&.readonly {
padding: $spacing-xxs;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useMemo } from 'react'

import classNames from 'classnames'

import { Col, Cols, ColType, Cycle, NodeValueValidation, Row } from 'meta/assessment'
Expand All @@ -11,12 +13,25 @@ type Props = {

export const useClassName = (props: Props): string => {
const { cycle, col, row, validation } = props
const { colType } = col.props

let className = ''
if (Cols.isReadOnly({ cycle, col, row })) className = 'calculated'
if ([ColType.text, ColType.textarea, ColType.select, ColType.taxon].includes(colType)) className = 'left'
if (colType === ColType.placeholder) className = 'category header left'
return useMemo<string>(() => {
const { colType } = col.props

const isPlaceholder = colType === ColType.placeholder
const isTextInput = [ColType.text, ColType.textarea, ColType.select, ColType.taxon].includes(colType)
const isCalculated = Cols.isCalculated({ col, row })
const isCalculatedInput = isCalculated && colType !== ColType.calculated
const isReadOnly = Cols.isReadOnly({ cycle, col, row }) && !isCalculatedInput

return classNames('table-grid__data-cell', className, { 'validation-error': !validation.valid })
return classNames(
'table-grid__data-cell',
{ 'validation-error': !validation.valid },
{
'calculated-input': isCalculatedInput && !isTextInput,
'category header left': isPlaceholder,
left: isTextInput,
readonly: isReadOnly,
}
)
}, [col, cycle, row, validation])
}
13 changes: 9 additions & 4 deletions src/client/pages/Section/DataTable/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import { useIsDataLocked } from 'client/store/ui/dataLock'
import { useCanEdit } from 'client/store/user'
import { useCanViewReview } from 'client/store/user/hooks'
import { useIsPrintRoute } from 'client/hooks/useIsRoute'
import ButtonTableExport from 'client/components/ButtonTableExport'
import { DataGrid } from 'client/components/DataGrid'
import { ButtonGridExport, DataGrid } from 'client/components/DataGrid'
import ButtonCopyValues from 'client/pages/Section/DataTable/Table/ButtonCopyValues'
import ButtonTableClear from 'client/pages/Section/DataTable/Table/ButtonTableClear'
import GridHeadCell from 'client/pages/Section/DataTable/Table/GridHeadCell'
Expand Down Expand Up @@ -40,6 +39,7 @@ const Table: React.FC<Props> = (props) => {

const { print } = useIsPrintRoute()
const tableRef = useRef<HTMLTableElement>(null)
const gridRef = useRef<HTMLDivElement>(null)

const { headers, noticeMessages, rowsData, rowsHeader, table, withReview } = useParsedTable({
assessmentName,
Expand All @@ -63,12 +63,17 @@ const Table: React.FC<Props> = (props) => {
<div className={classNames('fra-table__container', { 'fra-secondary-table__wrapper': secondary })}>
<div className="fra-table__scroll-wrapper">
<div className="fra-table__editor">
{!print && <ButtonTableExport filename={fileName} tableRef={tableRef} />}
{!print && <ButtonGridExport filename={fileName} gridRef={gridRef} />}
<ButtonCopyValues table={table} tableRef={tableRef} />
{canClearData && <ButtonTableClear disabled={disabled} sectionName={sectionName} table={table} />}
</div>

<DataGrid className="table-grid" gridTemplateColumns={gridTemplateColumns} withActions={withActions}>
<DataGrid
ref={gridRef}
className="table-grid"
gridTemplateColumns={gridTemplateColumns}
withActions={withActions}
>
{rowsHeader.map((row, rowIndex) => (
<React.Fragment key={row.uuid}>
{row.cols.map((col, colIndex) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,10 @@ const _fixFraTaxonCodes = async (client: BaseProtocol) => {
}

const _fixGrowingStockComposition2025HeaderRows = async (client: BaseProtocol) => {
const assessment = await AssessmentController.getOne({ assessmentName: AssessmentNames.fra }, client)
const { assessment, cycle } = await AssessmentController.getOneWithCycle(
{ assessmentName: AssessmentNames.fra, cycleName: '2025' },
client
)

const schemaAssessment = Schemas.getName(assessment)

Expand Down Expand Up @@ -771,7 +774,19 @@ const _fixGrowingStockComposition2025HeaderRows = async (client: BaseProtocol) =
join ${schemaAssessment}.table t on r.table_id = t.id
where r.props ->> 'index' = 'header_1'
and t.props ->> 'name' = 'growingStockComposition2025'
);`
);
update ${schemaAssessment}.col c
set props = jsonb_set(props, '{style,${cycle.uuid},rowSpan}', '1', false)
where c.props ->> 'colType' = 'header'
and c.row_id in (
select r.id from ${schemaAssessment}.row r
left join ${schemaAssessment}."table" t on t.id = r.table_id
where r.props ->> 'type' = 'header'
and r.props ->> 'index' = 'header_0'
and t.props ->> 'name' = 'growingStockComposition2025'
);
`
)
}

Expand Down

0 comments on commit 017964d

Please sign in to comment.