tangled
alpha
login
or
join now
mokkenstorm.dev
/
cv-manager
1
fork
atom
because I got bored of customising my CV for every job
1
fork
atom
overview
issues
pulls
pipelines
feat(client): add PDF export via print stylesheet
mokkenstorm.dev
2 months ago
2e033ffe
fefcfab6
+48
-9
2 changed files
expand all
collapse all
unified
split
apps
client
src
pages
CVViewPage.tsx
styles
print.css
+8
-9
apps/client/src/pages/CVViewPage.tsx
reviewed
···
6
6
useMeEducationQuery,
7
7
useMeJobExperienceQuery,
8
8
} from "@/generated/graphql";
9
9
+
import "@/styles/print.css";
9
10
10
11
export const CVViewPage = () => {
11
12
const { id } = useParams<{ id: string }>();
···
42
43
43
44
return (
44
45
<div className="min-h-screen bg-ctp-base">
45
45
-
<div className="bg-ctp-surface0 border-b border-ctp-surface1 py-4">
46
46
+
<div className="no-print bg-ctp-surface0 border-b border-ctp-surface1 py-4">
46
47
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-between">
47
48
<div>
48
49
<h1 className="text-2xl font-bold text-ctp-text">{cv.title}</h1>
···
51
52
</p>
52
53
</div>
53
54
<div className="flex gap-3">
55
55
+
<Button onClick={() => window.print()}>Export PDF</Button>
54
56
<ViewTransitionLink to={`/cvs/${cv.id}/edit`}>
55
55
-
<Button>Edit CV</Button>
57
57
+
<Button variant="outline">Edit CV</Button>
56
58
</ViewTransitionLink>
57
59
<ViewTransitionLink to="/cvs">
58
60
<Button variant="ghost">Back to List</Button>
···
62
64
</div>
63
65
64
66
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
65
65
-
<div className="bg-white rounded-lg shadow-lg p-8">
66
66
-
{/* CV Header */}
67
67
-
<div className="border-b border-gray-200 pb-6 mb-8">
67
67
+
<div className="cv-container bg-white rounded-lg shadow-lg p-8">
68
68
+
<div className="border-b border-gray-200 pb-6 mb-8 cv-entry">
68
69
<h1 className="text-4xl font-bold text-gray-900 mb-2">
69
70
{cv.title}
70
71
</h1>
···
75
76
)}
76
77
</div>
77
78
78
78
-
{/* Education Section */}
79
79
{loadingEducation ? (
80
80
<div className="py-8 text-center text-gray-500">
81
81
Loading education history...
···
94
94
{educations.map((education) => (
95
95
<div
96
96
key={education.id}
97
97
-
className="border-b border-gray-100 pb-6 last:border-0"
97
97
+
className="cv-entry border-b border-gray-100 pb-6 last:border-0"
98
98
>
99
99
<div className="flex justify-between items-start mb-2">
100
100
<div>
···
146
146
</div>
147
147
)}
148
148
149
149
-
{/* Job Experience Section */}
150
149
{loadingJobs ? (
151
150
<div className="py-8 text-center text-gray-500">
152
151
Loading job experience...
···
165
164
{jobExperiences.map((experience) => (
166
165
<div
167
166
key={experience.id}
168
168
-
className="border-b border-gray-100 pb-6 last:border-0"
167
167
+
className="cv-entry border-b border-gray-100 pb-6 last:border-0"
169
168
>
170
169
<div className="flex justify-between items-start mb-2">
171
170
<div>
+40
apps/client/src/styles/print.css
reviewed
···
1
1
+
@media print {
2
2
+
@page {
3
3
+
size: A4;
4
4
+
margin: 15mm 20mm;
5
5
+
}
6
6
+
7
7
+
body {
8
8
+
background: white !important;
9
9
+
color: black !important;
10
10
+
font-size: 11pt;
11
11
+
line-height: 1.4;
12
12
+
}
13
13
+
14
14
+
.no-print {
15
15
+
display: none !important;
16
16
+
}
17
17
+
18
18
+
.cv-container {
19
19
+
max-width: 100% !important;
20
20
+
padding: 0 !important;
21
21
+
margin: 0 !important;
22
22
+
box-shadow: none !important;
23
23
+
border-radius: 0 !important;
24
24
+
}
25
25
+
26
26
+
.cv-entry {
27
27
+
break-inside: avoid;
28
28
+
page-break-inside: avoid;
29
29
+
}
30
30
+
31
31
+
a {
32
32
+
text-decoration: none !important;
33
33
+
color: inherit !important;
34
34
+
}
35
35
+
36
36
+
h1, h2, h3 {
37
37
+
break-after: avoid;
38
38
+
page-break-after: avoid;
39
39
+
}
40
40
+
}