Skip to content

Commit

Permalink
[Closes #136] Layout QR codes for print (#142)
Browse files Browse the repository at this point in the history
  • Loading branch information
francisli authored Oct 18, 2024
1 parent 50490f9 commit 63bb8e7
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 13 deletions.
8 changes: 4 additions & 4 deletions client/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,11 @@ function App() {
/>
}
>
<Route
path="/admin/patients/generate"
element={<AdminPatientsGenerate />}
/>
<Route element={<Layout />}>
<Route
path="/admin/patients/generate"
element={<AdminPatientsGenerate />}
/>
<Route path="/patients/:patientId" element={<PatientDetails />} />
<Route
path="/patients/register/:patientId"
Expand Down
1 change: 1 addition & 0 deletions client/src/components/Sidebar/Sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const sections = [
{
label: 'QR Code',
href: '/admin/patients/generate',
target: '_blank',
icon: <IconQrcode stroke={2} />,
},
],
Expand Down
7 changes: 4 additions & 3 deletions client/src/components/Sidebar/SidebarNavSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function SidebarNavSection({
<Collapse in={opened} className={classes.section}>
{links.map((link) => (
<div className={classes.sublink} key={link.label}>
<SidebarLink icon={link.icon} label={link.label} href={link.href} />
<SidebarLink {...link} />
</div>
))}
</Collapse>
Expand All @@ -62,17 +62,18 @@ const SidebarLinkProps = {
icon: PropTypes.node,
href: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
target: PropTypes.string,
};

/**
* Navigation link for sidebar
* @param {PropTypes.InferProps<typeof SidebarLinkProps>} props
*/
export function SidebarLink({ icon, href, label }) {
export function SidebarLink({ icon, href, label, target = '_self' }) {
return (
<div className={classes.sublink}>
<div>{icon}</div>
<Text component={Link} className={classes.link} to={href}>
<Text component={Link} className={classes.link} to={href} target={target}>
{label}
</Text>
</div>
Expand Down
73 changes: 67 additions & 6 deletions client/src/pages/admin/patients/AdminPatientsGenerate.jsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,84 @@
import React from 'react';
import { Container, Loader } from '@mantine/core';
import React, { useState } from 'react';
import {
Box,
Button,
Container,
Flex,
Group,
Loader,
NativeSelect,
NumberInput,
} from '@mantine/core';
import { useQuery } from '@tanstack/react-query';
import { QRCode } from 'react-qrcode-logo';

import classes from './AdminPatientsGenerate.module.css';

/**
*
* @returns {React.ReactElement}
*/
function AdminPatientsGenerate() {
const [numPages, setNumPages] = useState(1);
const [layout, setLayout] = useState(12);
const { isLoading, data } = useQuery({
queryKey: ['generate', numPages, layout],
queryFn: () =>
fetch('/api/v1/patients/generate?count=12').then((response) =>
response.json(),
fetch(`/api/v1/patients/generate?count=${numPages * layout}`).then(
(response) => response.json(),
),
});

const pages = [];
if (data) {
for (let i = 0; i < numPages; i += 1) {
pages.push(data.slice(i * layout, (i + 1) * layout));
}
}

return (
<Container>
<Container className={classes.container}>
<Box className={classes.controls}>
<Group mb="1rem">
<NumberInput
label="Number of pages"
min={1}
value={numPages}
onChange={setNumPages}
/>
<NativeSelect
label="Layout"
value={layout}
onChange={(event) => setLayout(parseInt(event.target.value, 10))}
data={[
{ label: '2" x 2", 12 per 8.5" x 11"', value: '12' },
{ label: '2" x 2", 20 per 8.5" x 11"', value: '20' },
]}
/>
<Button onClick={() => window.print()} mt="1.625rem">
Print
</Button>
</Group>
<Box>Note: set scale to 100% and margins to 0 for proper printing.</Box>
</Box>
{isLoading && <Loader />}
{!isLoading && data?.map((url) => <QRCode key={url} value={url} />)}
{!isLoading &&
pages.map((p) => (
<>
<Flex
wrap="wrap"
className={`${classes.codes} ${classes[`codes--${layout}`]}`}
>
{p.map((url, i) => (
<>
<Box className={classes.code}>
<QRCode id={`qrcode-${i}`} key={url} value={url} />
</Box>
</>
))}
</Flex>
</>
))}
</Container>
);
}
Expand Down
70 changes: 70 additions & 0 deletions client/src/pages/admin/patients/AdminPatientsGenerate.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
.controls {
margin-bottom: 1rem;
}

.break {
flex-shrink: 0;
width: 100%;
height: 0;
}

[id^='qrcode-'] {
display: block;
margin: auto;
position: relative;
top: 50%;
transform: translateY(-50%);
}

.codes {
gap: 0;
break-after: page;
}

.code {
flex-shrink: 0;
}

.codes--12 {
width: 8.5in;
margin: 0;
padding: 0.3in 0.3125in;

.code {
width: 2in;
height: 2in;
margin: 0.3in 0.3125in;
}
}

.codes--20 {
width: 8.5in;
margin: 0 0 0.5in;
padding: 0.5in 0.25in 0;

.code {
width: 2in;
height: 2in;
margin: 0;
}
}

@media print {
@page {
size: letter;
margin: 0in;
}

.controls {
display: none;
}

.container {
margin: 0;
margin-inline: 0;
max-width: none;
padding: 0;
padding-inline: 0;
width: 8.5in;
}
}
10 changes: 10 additions & 0 deletions client/src/stories/Layout/layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,13 @@
max-width: 18rem;
}
}

@media print {
.layout {
min-height: none;
}

.layout-sidebar {
display: none;
}
}

0 comments on commit 63bb8e7

Please sign in to comment.