Skip to content

Commit

Permalink
3270 - Print (#3622)
Browse files Browse the repository at this point in the history
* 3270 - Update Print Style - PR 1 (#3620)

* 3270 - Add server side print

* 3270 - Update print buttons

* 3270 - Fix server side api handler

* 3270 - Fix deepscan

* 3270 - Use Responses.sendFile

* 3496 - Print: fix puppeteer page.waitForTimeout (#3653)

* 3270 - Print - Cache pdf (#3649)

* 3270 - Update yarn lock

* 3270 - Add country iso to Repository getOne props

* 3270 - Add country_summary getLastUpdate

* 3270 - Add FileRepository removeOne

* 3270 - Omit id and uuid unnecessary properties when creating a repository item

* 3270 - Add pdfRepository controller

* 3270 - Update print report endpoint logic

* 3270 - Fix Report naming and export

* 3270 - Fix yarn.lock

* 3270 - Remove country_iso from Repository.getOne props

* 3270 - Add CountrySummary getOneOrNone

* 3270 - Account for reports per language and with only tables

* 3270 - update force req param

* 3270 - remove FileRepository.removeOne

* 3270 - update file cleanup: call repo.remove only if uuids.length

* 3270 - restore file cleanup repeat pattern

---------

Co-authored-by: minotogna <[email protected]>

* 3270 - Fix screenshotDashboardCharts build page.waitForTimeout (#3662)

---------

Co-authored-by: Yhoan Alejandro Guzmán García <[email protected]>
  • Loading branch information
minotogna and yaguzmang authored Mar 27, 2024
1 parent 7103456 commit e94b0c8
Show file tree
Hide file tree
Showing 38 changed files with 938 additions and 214 deletions.
10 changes: 1 addition & 9 deletions jsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,7 @@
"compilerOptions": {
"module": "commonjs",
"target": "es2019",
"baseUrl": "./",
"paths": {
"i18n/*": ["./src/i18n/*"],
"client/*": ["./src/client/*"],
"meta/*": ["./src/meta/*"],
"server/*": ["./src/server/*"],
"test/*": ["./src/test/*"],
"utils/*": ["./src/utils/*"]
}
"baseUrl": "./src"
},
"include": ["src"]
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@
"null-loader": "^4.0.0",
"pdfjs-dist": "^2.13.216",
"prettier": "^2.6.1",
"puppeteer": "^13.5.2",
"react-refresh": "^0.11.0",
"sass": "^1.49.10",
"sass-loader": "^12.6.0",
Expand Down Expand Up @@ -231,6 +230,7 @@
"passport-local": "^1.0.0",
"pg": "^8.7.3",
"pg-promise": "^10.10.2",
"puppeteer": "^22.3.0",
"puppeteer-cluster": "^0.23.0",
"ramda": "^0.27.1",
"re-resizable": "^6.9.11",
Expand Down
32 changes: 30 additions & 2 deletions src/client/components/Buttons/Button/Button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
border: 1px solid;
cursor: pointer;
display: grid;
font-family: $font-family-1;
font-weight: 600;
line-height: 0;
justify-content: center;
Expand Down Expand Up @@ -77,9 +78,20 @@
}
}

.button__size-l {
font-size: $font-m;
height: $spacing-m;
padding: 0 11px;
grid-column-gap: $spacing-xxs;

svg {
height: 17px;
width: 17px;
}
}

// === type styles
@mixin withTheme($colorPrimary, $hoverInverseLighten) {
$colorSecondary: white;
@mixin withTheme($colorPrimary, $hoverInverseLighten, $colorSecondary: white) {
$hoverPercent: 4%;

background-color: $colorPrimary;
Expand Down Expand Up @@ -118,6 +130,22 @@
@include withTheme($ui-accent, 62%);
}

.button__type-anonymous {
@include withTheme($ui-border, 10%, transparent);

&.inverse {
svg {
color: $text-link;
}

&:hover {
svg {
color: darken($text-link, 5%);
}
}
}
}

.button__type-danger {
@include withTheme($ui-destructive, 58%);
}
15 changes: 12 additions & 3 deletions src/client/components/Buttons/Button/hooks/useButtonClassName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,27 @@ import classNames from 'classnames'
import { ButtonProps, ButtonSize, ButtonType } from 'client/components/Buttons/Button/types'

export const useButtonClassName = (props: ButtonProps): string => {
const { className, disabled, iconName, inverse, label, size = ButtonSize.s, type = ButtonType.primary } = props
const {
className,
disabled,
iconName,
inverse,
label,
noPrint = true,
size = ButtonSize.s,
type = ButtonType.primary,
} = props

return useMemo<string>(() => {
return classNames(
'button',
'no-print',
{ 'no-print': noPrint },
{ withIcon: Boolean(iconName) && Boolean(label) },
`button__size-${size}`,
`button__type-${type}`,
{ inverse },
{ disabled },
className
)
}, [className, disabled, iconName, inverse, label, size, type])
}, [className, disabled, iconName, inverse, label, noPrint, size, type])
}
3 changes: 3 additions & 0 deletions src/client/components/Buttons/Button/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ export enum ButtonSize {
xs = 'xs',
s = 's',
m = 'm',
l = 'l',
}

export enum ButtonType {
anonymous = 'anonymous',
danger = 'danger',
primary = 'primary',
}
Expand All @@ -15,6 +17,7 @@ export type ButtonProps = Pick<InputHTMLAttributes<HTMLButtonElement>, 'classNam
iconName?: string
inverse?: boolean
label?: React.ReactNode
noPrint?: boolean
size?: ButtonSize
type?: ButtonType
}
6 changes: 6 additions & 0 deletions src/client/components/EditorWYSIWYG/EditorWYSIWYG.scss
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,9 @@ div.editorWYSIWYG {
}
}
}

@include print {
div.editorWYSIWYG {
height: unset;
}
}
20 changes: 9 additions & 11 deletions src/client/components/PageLayout/Toolbar/LinksPrint/LinksPrint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,27 @@ import { Link } from 'react-router-dom'
import { Routes } from 'meta/routes'

import { useCountryRouteParams } from 'client/hooks/useRouteParams'
import { ButtonSize, ButtonType, useButtonClassName } from 'client/components/Buttons/Button'
import Icon from 'client/components/Icon'
import { Breakpoints } from 'client/utils'

const iconName = 'small-print'
const inverse = true
const size = ButtonSize.l
const type = ButtonType.anonymous

const LinksPrint: React.FC = () => {
const { assessmentName, cycleName, countryIso } = useCountryRouteParams()
const className = useButtonClassName({ iconName, inverse, size, type })

return (
<MediaQuery minWidth={Breakpoints.laptop}>
<Link
className="btn btn-secondary"
to={Routes.PrintTables.generatePath({ countryIso, assessmentName, cycleName })}
className={className}
target="_blank"
>
<Icon name="small-print" className="icon-margin-left icon-sub" />
<Icon name="icon-table2" className="icon-no-margin icon-sub" />
</Link>

<Link
className="btn btn-secondary"
to={Routes.Print.generatePath({ countryIso, assessmentName, cycleName })}
target="_blank"
>
<Icon name="small-print" className="icon-no-margin icon-sub" />
<Icon className="icon-no-margin icon-sub" name={iconName} />
</Link>
</MediaQuery>
)
Expand Down
8 changes: 8 additions & 0 deletions src/client/pages/Country/Country.scss
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@
}
}

@include print {
.app-view__content {
height: unset;
min-height: unset;
padding: unset;
}
}

.app-view__page-header {
display: flex;
flex-flow: row wrap;
Expand Down
36 changes: 36 additions & 0 deletions src/client/pages/Print/Header/Header.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
@import 'src/client/style/partials';

$toolbarHeight: $spacing-l;

.print-header {
display: grid;
align-items: center;
grid-row-gap: $spacing-xxs;
padding-top: $toolbarHeight;
}

.print-header__toolbar {
align-items: center;
background-color: $ui-bg;
border-bottom: 1px solid $ui-border;
display: flex;
gap: $spacing-xs;
height: $toolbarHeight;
justify-content: end;
left: 0;
padding: 0 $spacing-s;
position: fixed;
top: 0;
width: 100dvw;
z-index: 1;
}

@media print {
.print-header {
padding-top: 0;
}

.print-header__toolbar {
display: none;
}
}
74 changes: 74 additions & 0 deletions src/client/pages/Print/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import './Header.scss'
import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'

import { ApiEndPoint } from 'meta/api/endpoint'
import { AssessmentStatus, CountryIso } from 'meta/area'
import { Routes } from 'meta/routes'

import { useCountry } from 'client/store/area'
import { useIsPrintRoute } from 'client/hooks/useIsRoute'
import { useLanguage } from 'client/hooks/useLanguage'
import { useCountryRouteParams } from 'client/hooks/useRouteParams'
import { ButtonSize, useButtonClassName } from 'client/components/Buttons/Button'
import Icon from 'client/components/Icon'

const iconName = 'hit-down'
const label = 'Label'
const noPrint = false
const size = ButtonSize.m

const Header: React.FC = () => {
const { t } = useTranslation()
const { assessmentName, cycleName, countryIso } = useCountryRouteParams<CountryIso>()
const country = useCountry(countryIso)
const { onlyTables } = useIsPrintRoute()
const lang = useLanguage()

const downloadClassName = useButtonClassName({ iconName, label, noPrint, size })
const onlyTablesClassName = useButtonClassName({ iconName, inverse: !onlyTables, label, noPrint, size })

const params = new URLSearchParams({ assessmentName, countryIso, cycleName, lang, onlyTables: String(onlyTables) })
const downloadHref = `${ApiEndPoint.CycleData.Print.Report.one()}?${params.toString()}`
const PrintRoute = onlyTables ? Routes.Print : Routes.PrintTables

const { deskStudy, status } = country?.props ?? {}

const title = useMemo<string>(() => {
if (onlyTables) return t(`${assessmentName}.print.titleTables`, { cycleName })
if (deskStudy) return `${t(`assessment.${assessmentName}`)} ${t('assessment.deskStudy')}`
return t(`${assessmentName}.print.title`, { cycleName })
}, [assessmentName, cycleName, deskStudy, onlyTables, t])

const withDownload = ![AssessmentStatus.notStarted, AssessmentStatus.editing].includes(status)

return (
<div className="print-header">
<div className="print-header__toolbar">
{withDownload && (
<a className={downloadClassName} href={downloadHref} rel="noreferrer" target="_blank">
<Icon className="icon-white icon-sub" name="hit-down" />
<Icon className="icon-white icon-sub" name="icon-files" />
</a>
)}

{!onlyTables && (
<Link
className={onlyTablesClassName}
target="_blank"
to={PrintRoute.generatePath({ assessmentName, cycleName, countryIso })}
>
<Icon className="icon-white icon-sub" name="small-print" />
<Icon className="icon-white icon-sub" name="icon-table2" />
</Link>
)}
</div>

<h1>{t(`area.${countryIso}.listName`)}</h1>
<h1>{title}</h1>
</div>
)
}

export default Header
1 change: 1 addition & 0 deletions src/client/pages/Print/Header/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Header'
33 changes: 15 additions & 18 deletions src/client/pages/Print/Print.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { useTranslation } from 'react-i18next'
import { CountryIso } from 'meta/area/countryIso'
import { Labels } from 'meta/assessment'

import { useCountry } from 'client/store/area'
import { useCycle } from 'client/store/assessment'
import { useSections } from 'client/store/metadata'
import { useIsPrintRoute } from 'client/hooks/useIsRoute'
import { useCountryRouteParams } from 'client/hooks/useRouteParams'
import Loading from 'client/components/Loading'
import Header from 'client/pages/Print/Header'
import TableOfContent from 'client/pages/Print/TableOfContent'
import Section from 'client/pages/Section'

Expand All @@ -20,9 +20,8 @@ import { useGetTableSections } from './hooks/useGetTableSections'

const Print: React.FC = () => {
const { t } = useTranslation()
const { assessmentName, countryIso } = useCountryRouteParams<CountryIso>()
const { assessmentName } = useCountryRouteParams<CountryIso>()
const cycle = useCycle()
const country = useCountry(countryIso)
const sections = useSections()
const { onlyTables } = useIsPrintRoute()
useGetTableSections()
Expand All @@ -34,30 +33,22 @@ const Print: React.FC = () => {
document.body.style.height = 'unset'
}, [])

const deskStudy = country?.props?.deskStudy

if (!sections || !assessmentName) {
return <Loading />
}

let title = ''
if (onlyTables) title = t(`${assessmentName}.print.titleTables`, { cycleName: cycle.name })
if (!onlyTables && deskStudy) title = `${t(`assessment.${assessmentName}`)} ${t('assessment.deskStudy')}`
if (!onlyTables && !deskStudy) title = t(`${assessmentName}.print.title`, { cycleName: cycle.name })

return (
<div className="print__container">
<div className="print__header">
<h1>{t(`area.${countryIso}.listName`)}</h1>
<h1>{title}</h1>
</div>
<Header />

<hr />

{!onlyTables && <TableOfContent deskStudy={deskStudy} />}
{!onlyTables && <TableOfContent />}

{Object.values(sections).map((section) => {
{sections.map((section, sectionIdx) => {
const { subSections } = section
const sectionIndex = section.props.index

return (
<div key={section.uuid} id={`section${sectionIndex}`}>
{!onlyTables && (
Expand All @@ -67,8 +58,14 @@ const Print: React.FC = () => {
</h1>
)}

{Object.values(section.subSections).map((sectionItem) => {
return <Section key={sectionItem.uuid} section={sectionItem.props.name} />
{subSections.map((subSection, subSectionIdx) => {
const lastSection = sectionIdx === sections.length - 1 && subSectionIdx === subSections.length - 1
return (
<React.Fragment key={subSection.uuid}>
<Section section={subSection.props.name} />
{!lastSection && <div className="page-break" />}
</React.Fragment>
)
})}
</div>
)
Expand Down
Loading

0 comments on commit e94b0c8

Please sign in to comment.