Skip to content

Instantly share code, notes, and snippets.

@siteslave
Last active January 2, 2024 18:05
Show Gist options
  • Save siteslave/cc2f6e4b74465a0f0aac1e16403bc70a to your computer and use it in GitHub Desktop.
Save siteslave/cc2f6e4b74465a0f0aac1e16403bc70a to your computer and use it in GitHub Desktop.
<script>
	import { goto } from '$app/navigation';

	let isError = false;
	let isSuccess = false;
</script>

<svelte:head>
	<title>PDF</title>
</svelte:head>

<header class="flex flex-wrap md:justify-start md:flex-nowrap z-50 w-full text-sm">
	<nav
		class="mt-6 relative max-w-7xl w-full bg-white border border-gray-200 rounded-[36px] mx-2 py-3 px-4 md:flex md:items-center md:justify-between md:py-0 md:px-6 lg:px-8 xl:mx-auto dark:bg-gray-800 dark:border-gray-700"
		aria-label="Global"
	>
		<div class="flex items-center justify-between">
			<a class="flex-none text-xl font-semibold dark:text-white" href="#" aria-label="Brand"
				>My Application</a
			>
			<div class="md:hidden">
				<button
					type="button"
					class="hs-collapse-toggle p-2 inline-flex justify-center items-center gap-2 rounded-full border font-medium bg-white text-gray-700 shadow-sm align-middle hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-white focus:ring-blue-600 transition-all text-sm dark:bg-slate-900 dark:hover:bg-slate-800 dark:border-gray-700 dark:text-gray-400 dark:hover:text-white dark:focus:ring-offset-gray-800"
					data-hs-collapse="#navbar-collapse-with-animation"
					aria-controls="navbar-collapse-with-animation"
					aria-label="Toggle navigation"
				>
					<svg
						class="hs-collapse-open:hidden w-4 h-4"
						width="16"
						height="16"
						fill="currentColor"
						viewBox="0 0 16 16"
					>
						<path
							fill-rule="evenodd"
							d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"
						/>
					</svg>
					<svg
						class="hs-collapse-open:block hidden w-4 h-4"
						width="16"
						height="16"
						fill="currentColor"
						viewBox="0 0 16 16"
					>
						<path
							d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"
						/>
					</svg>
				</button>
			</div>
		</div>
		<div
			id="navbar-collapse-with-animation"
			class="hs-collapse hidden overflow-hidden transition-all duration-300 basis-full grow md:block"
		>
			<div
				class="flex flex-col gap-y-4 gap-x-0 mt-5 md:flex-row md:items-center md:justify-end md:gap-y-0 md:gap-x-7 md:mt-0 md:pl-7"
			>
				<a class="font-medium text-blue-600 md:py-6 dark:text-blue-500" href="#">Home</a>

				<button
					on:click={function () {
						goto('/logout');
					}}
					class="flex items-center gap-x-2 font-medium text-gray-500 hover:text-blue-600 md:border-l md:border-gray-300 md:my-6 md:pl-6 dark:border-gray-700 dark:text-gray-400 dark:hover:text-blue-500"
				>
					<svg
						class="w-4 h-4"
						xmlns="http://www.w3.org/2000/svg"
						width="16"
						height="16"
						fill="currentColor"
						viewBox="0 0 16 16"
					>
						<path
							d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0zm4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4zm-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664h10z"
						/>
					</svg>
					Log out
				</button>
			</div>
		</div>
	</nav>
</header>

<div class="md:container md:mx-auto">
	{#if isError}
		<div
			id="dismiss-alert"
			class="hs-removing:translate-x-5 hs-removing:opacity-0 transition duration-300 bg-red-50 border border-red-200 rounded-md p-4"
			role="alert"
		>
			<div class="flex">
				<div class="flex-shrink-0">
					<svg
						class="h-4 w-4 text-red-400 mt-0.5"
						xmlns="http://www.w3.org/2000/svg"
						width="16"
						height="16"
						fill="currentColor"
						viewBox="0 0 16 16"
					>
						<path
							d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"
						/>
					</svg>
				</div>
				<div class="ml-3">
					<div class="text-sm text-red-800 font-medium">เกิดข้อผิดพลาด กรุณาตรวจสอบ</div>
				</div>
				<div class="pl-3 ml-auto">
					<div class="-mx-1.5 -my-1.5">
						<button
							type="button"
							class="inline-flex bg-red-50 rounded-md p-1.5 text-red-500 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-red-50 focus:ring-red-600"
							data-hs-remove-element="#dismiss-alert"
						>
							<span class="sr-only">Dismiss</span>
							<svg
								class="h-3 w-3"
								width="16"
								height="16"
								viewBox="0 0 16 16"
								fill="none"
								xmlns="http://www.w3.org/2000/svg"
								aria-hidden="true"
							>
								<path
									d="M0.92524 0.687069C1.126 0.486219 1.39823 0.373377 1.68209 0.373377C1.96597 0.373377 2.2382 0.486219 2.43894 0.687069L8.10514 6.35813L13.7714 0.687069C13.8701 0.584748 13.9882 0.503105 14.1188 0.446962C14.2494 0.39082 14.3899 0.361248 14.5321 0.360026C14.6742 0.358783 14.8151 0.38589 14.9468 0.439762C15.0782 0.493633 15.1977 0.573197 15.2983 0.673783C15.3987 0.774389 15.4784 0.894026 15.5321 1.02568C15.5859 1.15736 15.6131 1.29845 15.6118 1.44071C15.6105 1.58297 15.5809 1.72357 15.5248 1.85428C15.4688 1.98499 15.3872 2.10324 15.2851 2.20206L9.61883 7.87312L15.2851 13.5441C15.4801 13.7462 15.588 14.0168 15.5854 14.2977C15.5831 14.5787 15.4705 14.8474 15.272 15.046C15.0735 15.2449 14.805 15.3574 14.5244 15.3599C14.2437 15.3623 13.9733 15.2543 13.7714 15.0591L8.10514 9.38812L2.43894 15.0591C2.23704 15.2543 1.96663 15.3623 1.68594 15.3599C1.40526 15.3574 1.13677 15.2449 0.938279 15.046C0.739807 14.8474 0.627232 14.5787 0.624791 14.2977C0.62235 14.0168 0.730236 13.7462 0.92524 13.5441L6.59144 7.87312L0.92524 2.20206C0.724562 2.00115 0.611816 1.72867 0.611816 1.44457C0.611816 1.16047 0.724562 0.887983 0.92524 0.687069Z"
									fill="currentColor"
								/>
							</svg>
						</button>
					</div>
				</div>
			</div>
		</div>
	{/if}

	{#if isSuccess}
		<div
			id="dismiss-alert"
			class="hs-removing:translate-x-5 hs-removing:opacity-0 transition duration-300 bg-teal-50 border border-teal-200 rounded-md p-4"
			role="alert"
		>
			<div class="flex">
				<div class="flex-shrink-0">
					<svg
						class="h-4 w-4 text-teal-400 mt-0.5"
						xmlns="http://www.w3.org/2000/svg"
						width="16"
						height="16"
						fill="currentColor"
						viewBox="0 0 16 16"
					>
						<path
							d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"
						/>
					</svg>
				</div>
				<div class="ml-3">
					<div class="text-sm text-teal-800 font-medium">อัปโหลดไฟล์เสร็จเรียบร้อย</div>
				</div>
				<div class="pl-3 ml-auto">
					<div class="-mx-1.5 -my-1.5">
						<button
							type="button"
							class="inline-flex bg-teal-50 rounded-md p-1.5 text-teal-500 hover:bg-teal-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-teal-50 focus:ring-teal-600"
							data-hs-remove-element="#dismiss-alert"
						>
							<span class="sr-only">Dismiss</span>
							<svg
								class="h-3 w-3"
								width="16"
								height="16"
								viewBox="0 0 16 16"
								fill="none"
								xmlns="http://www.w3.org/2000/svg"
								aria-hidden="true"
							>
								<path
									d="M0.92524 0.687069C1.126 0.486219 1.39823 0.373377 1.68209 0.373377C1.96597 0.373377 2.2382 0.486219 2.43894 0.687069L8.10514 6.35813L13.7714 0.687069C13.8701 0.584748 13.9882 0.503105 14.1188 0.446962C14.2494 0.39082 14.3899 0.361248 14.5321 0.360026C14.6742 0.358783 14.8151 0.38589 14.9468 0.439762C15.0782 0.493633 15.1977 0.573197 15.2983 0.673783C15.3987 0.774389 15.4784 0.894026 15.5321 1.02568C15.5859 1.15736 15.6131 1.29845 15.6118 1.44071C15.6105 1.58297 15.5809 1.72357 15.5248 1.85428C15.4688 1.98499 15.3872 2.10324 15.2851 2.20206L9.61883 7.87312L15.2851 13.5441C15.4801 13.7462 15.588 14.0168 15.5854 14.2977C15.5831 14.5787 15.4705 14.8474 15.272 15.046C15.0735 15.2449 14.805 15.3574 14.5244 15.3599C14.2437 15.3623 13.9733 15.2543 13.7714 15.0591L8.10514 9.38812L2.43894 15.0591C2.23704 15.2543 1.96663 15.3623 1.68594 15.3599C1.40526 15.3574 1.13677 15.2449 0.938279 15.046C0.739807 14.8474 0.627232 14.5787 0.624791 14.2977C0.62235 14.0168 0.730236 13.7462 0.92524 13.5441L6.59144 7.87312L0.92524 2.20206C0.724562 2.00115 0.611816 1.72867 0.611816 1.44457C0.611816 1.16047 0.724562 0.887983 0.92524 0.687069Z"
									fill="currentColor"
								/>
							</svg>
						</button>
					</div>
				</div>
			</div>
		</div>
	{/if}
	<div>
		<div class="grid grid-cols-2 gap-4 py-4">
			<div class="grid grid-cols-4 gap-4">
				<div class="col-span-2">
					<label class="block">
						<span class="sr-only">Choose profile photo</span>
						<input
							type="file"
							accept="image/*, application/pdf"
							on:change={() => {
								// Change file
							}}
							class="block w-full text-sm text-gray-500
      file:me-4 file:py-2 file:px-4
      file:rounded-lg file:border-0
      file:text-sm file:font-semibold
      file:bg-blue-600 file:text-white
      hover:file:bg-blue-700
      file:disabled:opacity-50 file:disabled:pointer-events-none
      dark:file:bg-blue-500
      dark:hover:file:bg-blue-400
    "
						/>
					</label>
				</div>
				<div>
					<select
						class="py-3 px-4 pe-9 block w-full border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-slate-900 dark:border-gray-700 dark:text-gray-400 dark:focus:ring-gray-600"
						on:change={() => {
							// change document type
						}}
					>
						<option value="" disabled selected>ประเภทไฟล์</option>
						<option value="1">ใบประกาศนียบัตร</option>
						<option value="2">โลโก้หน่วยงาน</option>
					</select>
				</div>
				<div>
					<button
						on:click={() => {
							// Upload file
						}}
						type="submit"
						class="py-3 px-4 inline-flex justify-center items-center gap-2 -ml-px rounded-lg border font-medium bg-white text-gray-700 align-middle hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-2 focus:ring-blue-600 transition-all text-sm dark:bg-gray-800 dark:hover:bg-slate-800 dark:border-gray-700 dark:text-gray-400"
					>
						อัปโหลด
					</button>
				</div>
			</div>

			<div class="flex justify-end items-center gap-x-2">
				<button
					on:click={function () {
						// Open signature dialog
					}}
					type="button"
					class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-semibold rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50 disabled:pointer-events-none dark:focus:outline-none dark:focus:ring-1 dark:focus:ring-gray-600"
				>
					ลายเซ็น
				</button>
				<button
					on:click={() => {
						// Export file
					}}
					type="button"
					class="py-3 px-4 inline-flex justify-center items-center gap-2 -ml-px rounded-lg border font-medium bg-white text-gray-700 align-middle hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-2 focus:ring-blue-600 transition-all text-sm dark:bg-gray-800 dark:hover:bg-slate-800 dark:border-gray-700 dark:text-gray-400"
				>
					ส่งออก (PDF)
				</button>
			</div>
		</div>
	</div>

	<div class="flex flex-col">
		<div class="-m-1.5 overflow-x-auto">
			<div class="p-1.5 min-w-full inline-block align-middle">
				<div
					class="border rounded-lg shadow overflow-hidden dark:border-gray-700 dark:shadow-gray-900"
				>
					<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
						<thead class="bg-gray-50 dark:bg-gray-700">
							<tr>
								<th
									scope="col"
									class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase dark:text-gray-400"
									>ชื่อ - สกุล</th
								>
								<th
									scope="col"
									class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase dark:text-gray-400"
									>ตำแหน่ง</th
								>
								<th
									scope="col"
									class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase dark:text-gray-400"
									>แผนก</th
								>
								<th
									scope="col"
									class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase dark:text-gray-400"
									>ใบประกาศ</th
								>
							</tr>
						</thead>
						<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
							<tr>
								<td
									class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-200"
								>
									xx xx</td
								>
								<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 dark:text-gray-200">
									xxx
								</td>
								<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 dark:text-gray-200">
									xxx
								</td>
								<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
									<div class="inline-flex rounded-md shadow-sm">
										<button
											on:click={function () {
												// show pdf
											}}
											type="button"
											class="py-3 px-4 inline-flex justify-center items-center gap-2 -ml-px first:rounded-l-lg first:ml-0 last:rounded-r-lg border font-medium bg-white text-gray-700 align-middle hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-2 focus:ring-blue-600 transition-all text-sm dark:bg-gray-800 dark:hover:bg-slate-800 dark:border-gray-700 dark:text-gray-400"
										>
											แสดง
										</button>
										<button
											on:click={function () {
												// download pdf
											}}
											type="button"
											class="py-3 px-4 inline-flex justify-center items-center gap-2 -ml-px first:rounded-l-lg first:ml-0 last:rounded-r-lg border font-medium bg-white text-gray-700 align-middle hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-2 focus:ring-blue-600 transition-all text-sm dark:bg-gray-800 dark:hover:bg-slate-800 dark:border-gray-700 dark:text-gray-400"
										>
											ดาวน์โหลด
										</button>
									</div>
								</td>
							</tr>
						</tbody>
					</table>
				</div>
			</div>
		</div>
	</div>
</div>
@siteslave
Copy link
Author

import db from '$lib/server/db';
import { makePDF } from '$lib/server/pdf';
import { readFileSync } from 'fs';
import { DateTime } from 'luxon';

/** @type {import('./$types').RequestHandler} */
export async function GET() {
	try {
		// Get employees
		const employees = await db('employees').orderBy('employee_id');
		let employeeData = [];
		// Header
		employeeData.push([
			{
				text: 'รหัส',
				fillColor: '#9DB2BF',
				border: [true, true, true, true],
				margin: 5
			},
			{
				text: 'ชื่อ - สกุล',
				fillColor: '#9DB2BF',
				border: [true, true, true, true],
				margin: 5
			},
			{
				text: 'ตำแหน่ง',
				fillColor: '#9DB2BF',
				border: [true, true, true, true],
				margin: 5
			},
			{
				text: 'แผนก',
				fillColor: '#9DB2BF',
				border: [true, true, true, true],
				margin: 5
			}
		]);

		// Rows
		employees.forEach((employee) => {
			const fullName = `${employee.first_name} ${employee.last_name}`;
			const row = [
				{
					text: employee.employee_id,
					border: [true, true, true, true],
					alignment: 'center'
				},
				{
					text: fullName,
					border: [true, true, true, true],
					alignment: 'left'
				},
				{
					text: employee.position,
					border: [true, true, true, true],
					alignment: 'left'
				},
				{
					text: employee.department,
					border: [true, true, true, true],
					alignment: 'left'
				}
			];

			employeeData.push(row);
		});
		// Get logo
		const logoFilePath = './templates/images/logo.png';
		const logoBuffer = readFileSync(logoFilePath);
		const logoBase64 = logoBuffer.toString('base64');

		const printTime = DateTime.now().toFormat('dd/MM/yyyy HH:mm:ss');

		// Pdf data
		var dataPdf = {
			watermark: {
				text: 'ห้ามเผยแพร่',
				color: 'red',
				fontSize: 40,
				opacity: 0.3,
				bold: true,
				angle: 0,
				italics: false
			},
			userPassword: '123',
			ownerPassword: '123456',
			permissions: {
				printing: 'highResolution', //'lowResolution'
				modifying: false,
				copying: false,
				annotating: true,
				fillingForms: true,
				contentAccessibility: true,
				documentAssembly: true
			},
			content: [
				{
					columns: [
						{
							image: `data:image/png;base64,${logoBase64}`,
							width: 64,
							margin: [10, 5, 0, 20],
							alignment: 'center'
						},
						{
							stack: [
								{
									text: 'xxxxxxxxx',
									color: '#333333',
									width: '*',
									fontSize: 12,
									bold: true,
									alignment: 'right'
									// margin: [0, 0, 0, 5],
								},
								{
									text: 'yyyyyyyyyyyyyy',
									color: '#333333',
									width: '*',
									fontSize: 8,
									bold: false,
									alignment: 'right'
									// margin: [0, 0, 0, 5],
								},
								{
									text: 'รายชื่อพนักงาน',
									color: '#333333',
									width: '*',
									fontSize: 16,
									bold: true,
									alignment: 'right'
									// margin: [0, 0, 0, 5],
								},
								{
									text: 'ข้อมูล ณ วันที่ xx xx xxxx',
									style: 'notesText',
									alignment: 'right'
								}
							]
						}
					]
				},
				{
					layout: {
						defaultBorder: false,
						hLineWidth: function () {
							return 1;
						},
						vLineWidth: function () {
							return 1;
						},
						hLineColor: function (/** @type {number} */ i) {
							if (i === 1 || i === 0) {
								return '#eeeeee';
							}
							return '#eeeeee';
						},
						vLineColor: function () {
							return '#eeeeee';
						},
						hLineStyle: function () {
							// if (i === 0 || i === node.table.body.length) {
							return null;
							//}
						},
						// vLineStyle: function (i, node) { return {dash: { length: 10, space: 4 }}; },
						paddingLeft: function () {
							return 10;
						},
						paddingRight: function () {
							return 10;
						},
						paddingTop: function () {
							return 2;
						},
						paddingBottom: function () {
							return 2;
						},
						fillColor: function () {
							return '#fff';
						}
					},
					table: {
						headerRows: 1,
						widths: [30, '*', '*', 100],
						body: employeeData
					}
				},
				'\n\n',
				{
					text: 'หมายเหตุ',
					style: 'notesTitle'
				},
				{
					text: 'คิวอาร์โค้ดเอกสารอ้างอิง',
					style: 'notesText'
				},
				{
					qr: 'http://localhost:5173/my/admin/pdf/report',
					foreground: 'green',
					background: 'white',
					fit: '80',
					margin: [0, 10]
				}
			],
			styles: {
				notesTitle: {
					fontSize: 10,
					bold: true,
					margin: [0, 50, 0, 3]
				},
				notesText: {
					fontSize: 9
				}
			},
			defaultStyle: {
				columnGap: 10,
				fontSize: 10
			},
			pageSize: 'A4',
			// [left, top, right, bottom] or [horizontal, vertical] or just a number for equal margins
			pageMargins: [40, 60, 40, 60],
			pageOrientation: 'portrait',
			/**
			 * Generates a footer for the given currentPage and pageCount.
			 *
			 * @param {number} currentPage - The current page number.
			 * @param {number} pageCount - The total number of pages.
			 * @return {any[]} An array containing the footer configuration.
			 */
			footer: function (currentPage, pageCount) {
				return [
					{
						width: '*',
						columns: [
							{
								text: 'วันที่พิมพ์ ' + printTime,
								margin: [40, 20, 0, 0],
								fontSize: 8
							},
							{
								text: 'หน้าที่ ' + currentPage.toString() + ' จาก ' + pageCount,
								alignment: 'right',
								margin: [0, 20, 40, 0],
								fontSize: 8
							}
						]
					}
				];
			},
			// pageOrientation: 'landscape',
			pageBreakBefore: function (
				/** @type {{ headlineLevel: number; }} */ currentNode,
				/** @type {string | any[]} */ followingNodesOnPage
			) {
				return currentNode.headlineLevel === 1 && followingNodesOnPage.length === 0;
			}
		};

		const pdfFilePath = await makePDF(dataPdf);
		const pdfBuffer = readFileSync(pdfFilePath);

		return new Response(pdfBuffer, {
			headers: {
				'Content-Type': 'application/pdf',
				'Content-Disposition': 'inline; filename="report.pdf"'
			}
		});
	} catch (error) {
		console.log(error);
		return new Response('Internal Server Error', { status: 500 });
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment