Skip to content

Commit

Permalink
feat: printing a calendar
Browse files Browse the repository at this point in the history
  • Loading branch information
asartalo committed Jul 12, 2021
1 parent 73e1102 commit 669e85f
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 64 deletions.
9 changes: 6 additions & 3 deletions cypress/integration/create_a_calendar.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable promise/no-nesting */
it('can create a calendar', () => {
cy.clock(Date.parse('July 8, 2021'));
cy.visitCalendar();
Expand Down Expand Up @@ -40,8 +41,10 @@ it('can create a calendar', () => {

void cy.window().then((win) => {
const printStub = cy.stub(win, 'print');
cy.findByRole('button', { name: /print/i }).click();
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
expect(printStub).to.be.called;

void cy.findByRole('button', { name: /print/i }).click().then(() => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
expect(printStub).to.be.called;
});
});
});
20 changes: 14 additions & 6 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import { makeStyles } from '@material-ui/core';
import { createMuiTheme, makeStyles, MuiThemeProvider } from '@material-ui/core';
import CalendarPage from '../pages/calendar/CalendarPage';
import MainPage from '../pages/main/MainPage';
import BaseStyle from './BaseStyle';
Expand All @@ -19,19 +19,27 @@ const styles = makeStyles((theme) => ({
},
}));

const theme = createMuiTheme({
palette: {
background: {
default: '#f3f3f3',
},
},
});

function App(): JSX.Element {
const classes = styles();
return (
<>
<Router basename={basePath}>
<Router basename={basePath}>
<MuiThemeProvider theme={theme}>
<BaseStyle />
<PrintablesAppBar />
<main className={classes.main}>
<main className={`${classes.main} print-ignore`}>
<Route exact path="/" component={MainPage} />
<Route exact path="/calendar" component={CalendarPage} />
</main>
</Router>
</>
</MuiThemeProvider>
</Router>
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/PrintablesAppBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const styles = makeStyles(() => ({
const PrintablesAppBar = (): JSX.Element => {
const classes = styles();
return (
<div className={classes.wrap}>
<div className={`${classes.wrap} no-print`}>
<AppBar position="fixed">
<Toolbar>
<IconButton edge="start" color="inherit" aria-label="menu">
Expand Down
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom';
import App from './components/App';
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
import reportWebVitals from './reportWebVitals';
import './printing.css';

ReactDOM.render(
<React.StrictMode>
Expand Down
26 changes: 21 additions & 5 deletions src/pages/calendar/CalendarPage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import React, { useState } from 'react';
import React, {
useState,
} from 'react';
import {
Typography, Grid, makeStyles,
} from '@material-ui/core';
import CustomizeCalendarForm from './CustomizeCalendarForm';
import CalendarData from './CalendarData';
import PreviewCalendar from './PreviewCalendar';
import PaperPreview from './PaperPreview';

const pageStyles = makeStyles((theme) => ({
container: {
Expand All @@ -18,6 +21,16 @@ const pageStyles = makeStyles((theme) => ({
},
column: {
},
main: {
overflow: 'auto',

'@media print': {
width: '100% !important',
maxWidth: 'none',
flexBasis: 'auto',
overflow: 'visible',
},
},
sideColumn: {
paddingTop: theme.spacing(2),
},
Expand All @@ -31,6 +44,7 @@ const CalendarPage = (): JSX.Element => {
}) as CalendarData);
const onPrint = (data: CalendarData): void => {
setCalendarData({ ...data });
window.print();
};
const onChange = (data: CalendarData): void => {
setCalendarData({ ...data });
Expand All @@ -39,8 +53,8 @@ const CalendarPage = (): JSX.Element => {
const classes = pageStyles();

return (
<Grid container spacing={3} className={classes.container}>
<Grid item sm={3} className={classes.column}>
<Grid container spacing={3} className={`${classes.container} print-ignore`}>
<Grid item xs={3} sm={2} className={`${classes.column} no-print`}>
<section aria-label="Customize Calendar" className={classes.sideColumn}>
<Typography variant="h5" component="h1">Calendar</Typography>
<CustomizeCalendarForm
Expand All @@ -50,9 +64,11 @@ const CalendarPage = (): JSX.Element => {
/>
</section>
</Grid>
<Grid item sm={9} className={classes.column}>
<Grid item xs={9} sm={10} className={`${classes.main} print-ignore`}>
<section aria-label="Preview">
<PreviewCalendar calendarData={calendarData} />
<PaperPreview>
<PreviewCalendar calendarData={calendarData} />
</PaperPreview>
</section>
</Grid>
</Grid>
Expand Down
91 changes: 91 additions & 0 deletions src/pages/calendar/PaperPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React, { ReactNode } from 'react';
import { makeStyles, Paper } from '@material-ui/core';
import { CSSProperties } from '@material-ui/core/styles/withStyles';

const paperPreviewStyles = makeStyles(() => ({
paper: {
margin: '0 auto 20px',
position: 'relative',
overflow: 'hidden',

'@media print': {
borderRadius: 0,
boxShadow: 'none',
margin: 0,
},
},

content: {
boxSizing: 'border-box',
display: 'flex',
flexGrow: 1,
// position: 'absolute',
// top: 0,
// left: 0,
transformOrigin: 'top left',

'& > h1:first-child': {
marginTop: 0,
},
},
}));

interface PaperPreviewProps {
children: ReactNode;
margin?: string | number;
orientation?: string;
scale?: number;
}

function PaperPreview({
children, margin = '10mm', orientation = 'landscape', scale = 1,
}: PaperPreviewProps): JSX.Element {
const classes = paperPreviewStyles();
const paperMargin = typeof margin === 'number' ? `${margin}mm` : margin;
const paperStyle: CSSProperties = orientation === 'landscape'
? {
aspectRatio: '297 / 210',
width: '297mm',
}
: {
aspectRatio: '210 / 297',
width: '210mm',
};

if (scale !== 1) {
paperStyle.transform = `scale(${scale})`;
}

const contentStyles = orientation === 'landscape'
? {
width: '297mm',
height: '210mm',
}
: {
width: '210mm',
height: '297mm',
};

return (
<Paper
id="printable-paper"
className={`printable-paper ${classes.paper}`}
style={paperStyle}
>
<div
className={`${classes.content} printable-paper-content`}
style={{ padding: paperMargin, ...contentStyles }}
>
{children}
</div>
</Paper>
);
}

PaperPreview.defaultProps = {
margin: '10mm',
orientation: 'landscape',
scale: 1,
};

export default PaperPreview;
83 changes: 34 additions & 49 deletions src/pages/calendar/PreviewCalendar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable react/no-array-index-key */
import { Box, makeStyles } from '@material-ui/core';
import React, { useEffect } from 'react';
import React from 'react';

import CalendarData from './CalendarData';
import { DateNumber, getWeekDates } from './previewUtils';
Expand All @@ -25,14 +25,23 @@ const previewStyles = makeStyles((theme) => ({
},
wrap: {
textAlign: 'center',
display: 'flex',
flexFlow: 'column',
},
contentWrap: {
flexGrow: 1,
},
calendar: {
width: '100%',
border: '1px solid #666',
borderCollapse: 'collapse',
tableLayout: 'fixed',
height: '100%',
},
headers: {
'& > tr': {
height: '36px',
},
'& th': {
textTransform: 'uppercase',
fontWeight: 'normal',
Expand Down Expand Up @@ -84,57 +93,33 @@ function PreviewCalendar(props: PreviewCalendarProps): JSX.Element {
const weeks = getWeekDates(referenceDate);
const classes = previewStyles();

const calculateCellHeight = (): void => {
const body = document.querySelector('.calendar-body[aria-label="Dates"]') as HTMLElement;
const vHeight = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
let y = 0;
let node = body;
while (node) {
y += node.offsetTop;
if (node.offsetParent === null) {
break;
} else {
node = node.offsetParent as HTMLElement;
}
}
const bodyHeight = vHeight - y - 64;
const cellHeight = bodyHeight / weeks.length;
body.querySelectorAll('td').forEach((cell) => {
// eslint-disable-next-line no-param-reassign
cell.style.height = `${Math.round(cellHeight)}px`;
});
};

useEffect(() => {
calculateCellHeight();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [weeks.length]);

return (
<Box className={classes.wrap}>
<h1 className={classes.title}>{ dateFormat.format(referenceDate) }</h1>
<table className={classes.calendar}>
<thead className={classes.headers}>
<tr>
<th>Sunday</th>
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
</tr>
</thead>
<tbody aria-label="Dates" className={`${classes.body} calendar-body`}>
{
weeks.map((week, index) => (
<tr key={`week-${index}`}>
{ dateCells(week, index) }
</tr>
))
}
</tbody>
</table>
<div className={classes.contentWrap}>
<table className={classes.calendar}>
<thead className={classes.headers}>
<tr>
<th>Sunday</th>
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
</tr>
</thead>
<tbody aria-label="Dates" className={`${classes.body} calendar-body`}>
{
weeks.map((week, index) => (
<tr key={`week-${index}`}>
{ dateCells(week, index) }
</tr>
))
}
</tbody>
</table>
</div>
</Box>
);
}
Expand Down
20 changes: 20 additions & 0 deletions src/printing.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@page {
margin: 0;
}

@media print {
.no-print {
display: none;
}

.print-ignore {
margin: 0 !important;
padding: 0 !important;
overflow: visible;
}

.printable-paper {
margin: 0 auto;
display: auto;
}
}

0 comments on commit 669e85f

Please sign in to comment.