Skip to content

Commit

Permalink
Merge pull request #36 from SkyLightQP/feature/pdf
Browse files Browse the repository at this point in the history
feat: implement exporting pdf by print
  • Loading branch information
SkyLightQP authored Feb 11, 2025
2 parents 97f7248 + 5c6fb40 commit 3674c04
Show file tree
Hide file tree
Showing 10 changed files with 350 additions and 93 deletions.
2 changes: 1 addition & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "1.0.0",
"homepage": "https://daegyeo.me",
"scripts": {
"dev": "next dev",
"dev": "next dev --turbo",
"build": "next build",
"start": "next start",
"lint": "eslint . --ext .ts,.tsx",
Expand All @@ -22,12 +22,13 @@
"@supabase/ssr": "^0.5.1",
"@supabase/supabase-js": "^2.46.1",
"framer-motion": "^6.3.12",
"next": "^15.0.2",
"next": "^15.1.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.33.1",
"react-hotkeys-hook": "^3.4.6",
"react-markdown": "^9.0.1",
"react-to-print": "^3.0.5",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"remark-gfm": "^4.0.0",
Expand Down
15 changes: 15 additions & 0 deletions src/acitons/section-data.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,18 @@ export const getSectionData = async () => {
error
};
};

export const getPdfSectionData = async () => {
const supabase = await createSupabaseClient();
const { data, error } = await supabase
.from('sections')
.select('*, contents(*, links(*), images(*))')
.eq('showPdf', true)
.eq('contents.isHidden', false)
.eq('contents.showPdf', true)
.order('id', { ascending: true });
return {
sections: data as SectionType,
error
};
};
45 changes: 34 additions & 11 deletions src/app/admin/content/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ interface AddForm {
readonly section: string;
readonly hasMargin: boolean;
readonly isHidden: boolean;
readonly showPdf: boolean;
}

const DEFAULT_VALUE: AddForm = {
Expand All @@ -46,7 +47,8 @@ const DEFAULT_VALUE: AddForm = {
description: '',
section: '',
hasMargin: true,
isHidden: false
isHidden: false,
showPdf: false
};

const Page: React.FC = () => {
Expand Down Expand Up @@ -161,25 +163,41 @@ const Page: React.FC = () => {
`}
{...register('subtitle')}
/>
<Select placeholder="섹션" background="white" {...register('section', { required: true })}>
<SectionOptions />
</Select>

<div
style={{
gridColumn: '1 / 3',
marginRight: '26px'
}}
>
<Select placeholder="섹션" background="white" {...register('section', { required: true })}>
<SectionOptions />
</Select>
</div>
<Checkbox
css={css`
grid-column: 4 / 5;
grid-column: 3 / 4;
`}
{...register('hasMargin')}
>
컨텐츠 간 간격 추가
</Checkbox>
<Checkbox
css={css`
grid-column: 5 / 6;
grid-column: 4 / 5;
`}
{...register('isHidden')}
>
숨김
</Checkbox>
<Checkbox
css={css`
grid-column: 5 / 6;
`}
{...register('showPdf')}
>
PDF 보이기
</Checkbox>
<Textarea
placeholder="내용"
background="white"
Expand Down Expand Up @@ -221,7 +239,8 @@ const Page: React.FC = () => {
description: item.description,
section: String(item.sections.id),
hasMargin: item.hasMargin,
isHidden: item.isHidden
isHidden: item.isHidden,
showPdf: item.showPdf
}
});
updateDialog.onOpen();
Expand All @@ -235,7 +254,8 @@ const Page: React.FC = () => {
description: item.description,
section: String(item.sections.id),
hasMargin: item.hasMargin,
isHidden: item.isHidden
isHidden: item.isHidden,
showPdf: item.showPdf
}
});
deleteDialog.onOpen();
Expand Down Expand Up @@ -280,15 +300,17 @@ const Page: React.FC = () => {
{ id: 'description', label: '내용', component: <Textarea height="sm" /> },
{ id: 'section', label: '섹션', component: <Select />, option: <SectionOptions /> },
{ id: 'hasMargin', label: '컨텐츠 간 간격 추가', component: <Checkbox /> },
{ id: 'isHidden', label: '숨김', component: <Checkbox /> }
{ id: 'isHidden', label: '숨김', component: <Checkbox /> },
{ id: 'showPdf', label: 'PDF 보이기', component: <Checkbox /> }
]}
defaultValue={[
modalData.value.title,
modalData.value.subtitle,
modalData.value.description,
modalData.value.section,
modalData.value.hasMargin,
modalData.value.isHidden
modalData.value.isHidden,
modalData.value.showPdf
]}
onUpdateClick={async (values) => {
await supabase
Expand All @@ -299,7 +321,8 @@ const Page: React.FC = () => {
description: values.description,
sectionId: Number(values.section),
hasMargin: values.hasMargin,
isHidden: values.isHidden
isHidden: values.isHidden,
showPdf: values.showPdf
})
.match({ id: modalData.id });
await fetchData();
Expand Down
29 changes: 17 additions & 12 deletions src/app/admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import React, { useCallback, useEffect, useState } from 'react';
import styled from '@emotion/styled';
import { Button, Input, useDisclosure, useToast } from '@chakra-ui/react';
import { Button, Checkbox, Input, useDisclosure, useToast } from '@chakra-ui/react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { DragEndEvent } from '@dnd-kit/core';
import { arrayMove } from '@dnd-kit/sortable';
Expand All @@ -29,8 +29,12 @@ const Footer = styled.div`
const Page: React.FC = () => {
const [data, setData] = useState<Array<SchemaType<'sections'>>>([]);
const [isChange, setBeChange] = useState(false);
const [modalData, setModalData] = useState<{ id: number; title: string }>({ id: -1, title: '' });
const { register, handleSubmit, reset } = useForm<{ title: string }>();
const [modalData, setModalData] = useState<{ id: number; title: string; showPdf: boolean }>({
id: -1,
title: '',
showPdf: false
});
const { register, handleSubmit, reset } = useForm<{ title: string; showPdf: boolean }>();
const deleteDialog = useDisclosure();
const updateDialog = useDisclosure();
const supabase = createSupabaseClient();
Expand Down Expand Up @@ -110,15 +114,16 @@ const Page: React.FC = () => {
data={data}
columns={[
{ key: 'title', label: '제목' },
{ key: 'showPdf', label: 'PDF 보이기' },
{ key: 'createdAt', label: '생성일', isDate: true }
]}
onDragEnd={onChangeData}
onTableUpdateClick={(item) => {
setModalData({ id: item.id, title: item.title });
setModalData({ id: item.id, title: item.title, showPdf: item.showPdf });
updateDialog.onOpen();
}}
onTableDeleteClick={(item) => {
setModalData({ id: item.id, title: item.title });
setModalData({ id: item.id, title: item.title, showPdf: item.showPdf });
deleteDialog.onOpen();
}}
/>
Expand Down Expand Up @@ -148,16 +153,16 @@ const Page: React.FC = () => {
<UpdateModal
modalController={updateDialog}
fields={[
{
id: 'title',
label: '제목',
component: <Input />
}
{ id: 'title', label: '제목', component: <Input /> },
{ id: 'showPdf', label: 'PDF 보이기', component: <Checkbox /> }
]}
defaultValue={[modalData.title]}
defaultValue={[modalData.title, modalData.showPdf]}
onUpdateClick={async (values) => {
if (values.title.trim() === '') return;
await supabase.from('sections').update({ title: values.title }).match({ id: modalData.id });
await supabase
.from('sections')
.update({ title: values.title, showPdf: values.showPdf })
.match({ id: modalData.id });
await fetchData();
updateDialog.onClose();
}}
Expand Down
25 changes: 23 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
'use client';

import React, { useEffect, useState } from 'react';
import styled from '@emotion/styled';
import React, { useEffect, useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useToast } from '@chakra-ui/react';
import { PostgrestError } from '@supabase/supabase-js';
import { useRouter } from 'next/navigation';
import { useReactToPrint } from 'react-to-print';
import styled from '@emotion/styled';
import Landing from '../components/Landing';
import { Space } from '../components/Space';
import { LargeContentText, LargeHintedText, SectionTitle } from '../components/Typography';
Expand All @@ -14,6 +15,7 @@ import { ExternalLinkView } from '../components/ContentView/ExternalLinkView';
import { DescriptionView } from '../components/ContentView/DescriptionView';
import { ImageView } from '../components/ContentView/ImageView';
import { getSectionData, SectionType } from '../acitons/section-data.action';
import { PdfView } from '../components/PdfView';

const Container = styled.div`
margin: 8rem 172px;
Expand Down Expand Up @@ -45,6 +47,8 @@ const Page: React.FC = () => {
isClosable: true,
position: 'top-left'
});
const pdfRef = useRef<HTMLDivElement>(null);
const handlePrint = useReactToPrint({ contentRef: pdfRef });

useHotkeys('a+d', () => {
router.push('/admin');
Expand All @@ -54,6 +58,21 @@ const Page: React.FC = () => {
getSectionData().then(setData);
}, []);

useEffect(() => {
const printKeyListener = (e: KeyboardEvent) => {
if (e.key === 'p' && e.ctrlKey) {
e.preventDefault();
handlePrint();
}
};

window.addEventListener('keydown', printKeyListener);

return () => {
window.removeEventListener('keydown', printKeyListener);
};
}, []);

useEffect(() => {
if (error !== null) {
toast({
Expand Down Expand Up @@ -108,6 +127,8 @@ const Page: React.FC = () => {
)}
<SocialLinkView />
</Container>

<PdfView ref={pdfRef} />
</>
);
};
Expand Down
5 changes: 3 additions & 2 deletions src/components/ContentView/ExternalLinkView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { SchemaType } from '../../../types/type-util';

interface ExternalLinkViewProps {
readonly links: SchemaType<'links'>[];
readonly isPrint?: boolean;
}

const ExternalLinkGroup = styled.div`
Expand All @@ -22,14 +23,14 @@ const ExternalLinkGroup = styled.div`
}
`;

export const ExternalLinkView: FC<ExternalLinkViewProps> = ({ links }) => {
export const ExternalLinkView: FC<ExternalLinkViewProps> = ({ links, isPrint }) => {
return (
<ExternalLinkGroup>
{links
.sort((a, b) => a.order - b.order)
.map((link) => (
<ExternalLink key={link.id} href={link.href}>
{link.name}
{isPrint ? `${link.name}: ${link.href}` : link.name}
</ExternalLink>
))}
</ExternalLinkGroup>
Expand Down
Loading

0 comments on commit 3674c04

Please sign in to comment.