Fix adoption report photos and section order
This commit is contained in:
+34
-2
@@ -1353,6 +1353,37 @@ const deleteBirdPhotoObjectIfNeeded = async (objectKey: string | null) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadBirdReportPhotoBuffer = async (bird: BirdRow) => {
|
||||||
|
if (!bird.photo_object_key) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const s3Config = getS3ImageStorageConfig();
|
||||||
|
|
||||||
|
if (!s3Config) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const signedUrl = getSignedS3ObjectUrl({
|
||||||
|
config: s3Config,
|
||||||
|
objectKey: bird.photo_object_key,
|
||||||
|
expiresInSeconds: 5 * 60,
|
||||||
|
});
|
||||||
|
const imageResponse = await fetch(signedUrl);
|
||||||
|
|
||||||
|
if (!imageResponse.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = imageResponse.headers.get('content-type') || bird.photo_content_type || '';
|
||||||
|
|
||||||
|
if (!/^image\/(?:png|jpe?g)$/i.test(contentType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer.from(await imageResponse.arrayBuffer());
|
||||||
|
};
|
||||||
|
|
||||||
const getDefaultBirdPhotoAttachment = () => {
|
const getDefaultBirdPhotoAttachment = () => {
|
||||||
const defaultPhotoPath = path.join(process.cwd(), 'assets', 'yoda-default.png');
|
const defaultPhotoPath = path.join(process.cwd(), 'assets', 'yoda-default.png');
|
||||||
|
|
||||||
@@ -3408,10 +3439,11 @@ app.post(
|
|||||||
throw new Error('Unable to create bird transfer code.');
|
throw new Error('Unable to create bird transfer code.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const [weights, vetVisits, notes] = await Promise.all([
|
const [weights, vetVisits, notes, birdPhotoBuffer] = await Promise.all([
|
||||||
listWeightsForBird(sourceBird.id, req.auth!.workspace.id, adoptionReportWeightHistoryDays),
|
listWeightsForBird(sourceBird.id, req.auth!.workspace.id, adoptionReportWeightHistoryDays),
|
||||||
listVetVisitsForBird(sourceBird.id, req.auth!.workspace.id),
|
listVetVisitsForBird(sourceBird.id, req.auth!.workspace.id),
|
||||||
listFlockNotes(req.auth!.workspace.id),
|
listFlockNotes(req.auth!.workspace.id),
|
||||||
|
loadBirdReportPhotoBuffer(sourceBird),
|
||||||
]);
|
]);
|
||||||
const birdNotes = notes.filter((note) => note.bird_id === sourceBird.id);
|
const birdNotes = notes.filter((note) => note.bird_id === sourceBird.id);
|
||||||
const pdf = await renderAdoptionReportPdf({
|
const pdf = await renderAdoptionReportPdf({
|
||||||
@@ -3419,8 +3451,8 @@ app.post(
|
|||||||
weights,
|
weights,
|
||||||
vetVisits,
|
vetVisits,
|
||||||
notes: birdNotes,
|
notes: birdNotes,
|
||||||
workspace: req.auth!.workspace,
|
|
||||||
transferCode: transferCode.code,
|
transferCode: transferCode.code,
|
||||||
|
birdPhotoBuffer,
|
||||||
printFriendly: req.query.printFriendly === 'true',
|
printFriendly: req.query.printFriendly === 'true',
|
||||||
assets: {
|
assets: {
|
||||||
logoPath: path.join(process.cwd(), 'assets', 'flockpal-logo.png'),
|
logoPath: path.join(process.cwd(), 'assets', 'flockpal-logo.png'),
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import fs from 'fs';
|
|||||||
import PDFDocument from 'pdfkit';
|
import PDFDocument from 'pdfkit';
|
||||||
import QRCode from 'qrcode';
|
import QRCode from 'qrcode';
|
||||||
|
|
||||||
import type { BirdRow, FlockNoteRow, VetVisitRow, WeightRow, WorkspaceRow } from '../types.js';
|
import type { BirdRow, FlockNoteRow, VetVisitRow, WeightRow } from '../types.js';
|
||||||
|
|
||||||
type AdoptionReportInput = {
|
type AdoptionReportInput = {
|
||||||
bird: BirdRow;
|
bird: BirdRow;
|
||||||
weights: WeightRow[];
|
weights: WeightRow[];
|
||||||
vetVisits: VetVisitRow[];
|
vetVisits: VetVisitRow[];
|
||||||
notes: FlockNoteRow[];
|
notes: FlockNoteRow[];
|
||||||
workspace: WorkspaceRow;
|
|
||||||
transferCode: string;
|
transferCode: string;
|
||||||
|
birdPhotoBuffer?: Buffer | null;
|
||||||
assets: {
|
assets: {
|
||||||
logoPath: string;
|
logoPath: string;
|
||||||
wordmarkPath: string;
|
wordmarkPath: string;
|
||||||
@@ -256,8 +256,8 @@ export const renderAdoptionReportPdf = async ({
|
|||||||
weights,
|
weights,
|
||||||
vetVisits,
|
vetVisits,
|
||||||
notes,
|
notes,
|
||||||
workspace,
|
|
||||||
transferCode,
|
transferCode,
|
||||||
|
birdPhotoBuffer = null,
|
||||||
assets,
|
assets,
|
||||||
printFriendly = false,
|
printFriendly = false,
|
||||||
}: AdoptionReportInput) => {
|
}: AdoptionReportInput) => {
|
||||||
@@ -275,7 +275,7 @@ export const renderAdoptionReportPdf = async ({
|
|||||||
const logoPath = fs.existsSync(assets.logoPath) ? assets.logoPath : null;
|
const logoPath = fs.existsSync(assets.logoPath) ? assets.logoPath : null;
|
||||||
const wordmarkPath = fs.existsSync(assets.wordmarkPath) ? assets.wordmarkPath : logoPath;
|
const wordmarkPath = fs.existsSync(assets.wordmarkPath) ? assets.wordmarkPath : logoPath;
|
||||||
const defaultPhotoPath = fs.existsSync(assets.defaultBirdPhotoPath) ? assets.defaultBirdPhotoPath : null;
|
const defaultPhotoPath = fs.existsSync(assets.defaultBirdPhotoPath) ? assets.defaultBirdPhotoPath : null;
|
||||||
const photoBuffer = dataUrlToBuffer(bird.photo_data_url);
|
const photoBuffer = birdPhotoBuffer ?? dataUrlToBuffer(bird.photo_data_url);
|
||||||
const contentWidth = page.width - page.margin * 2;
|
const contentWidth = page.width - page.margin * 2;
|
||||||
const headerY = page.margin;
|
const headerY = page.margin;
|
||||||
const headerHeight = 136;
|
const headerHeight = 136;
|
||||||
@@ -346,21 +346,6 @@ export const renderAdoptionReportPdf = async ({
|
|||||||
);
|
);
|
||||||
y += 72;
|
y += 72;
|
||||||
|
|
||||||
y = drawSectionTitle(doc, 'Weight Graph', y);
|
|
||||||
drawSimpleWeightChart(doc, weights, bird.chart_color, page.margin, y, contentWidth, 120);
|
|
||||||
y += 140;
|
|
||||||
|
|
||||||
y = drawSectionTitle(doc, 'Weight History', y);
|
|
||||||
y = drawTable(
|
|
||||||
doc,
|
|
||||||
['Date', 'Weight', 'Notes'],
|
|
||||||
weights.length ? weights.map((entry) => [formatDate(entry.recorded_on), formatWeight(entry.weight_grams), entry.notes || '']) : [['No weights recorded.', '', '']],
|
|
||||||
page.margin,
|
|
||||||
y,
|
|
||||||
[95, 70, contentWidth - 165],
|
|
||||||
24,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (y > 610) {
|
if (y > 610) {
|
||||||
doc.addPage();
|
doc.addPage();
|
||||||
y = page.margin;
|
y = page.margin;
|
||||||
@@ -388,6 +373,25 @@ export const renderAdoptionReportPdf = async ({
|
|||||||
28,
|
28,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (y > 575) {
|
||||||
|
doc.addPage();
|
||||||
|
y = page.margin;
|
||||||
|
}
|
||||||
|
y = drawSectionTitle(doc, 'Weight Graph', y);
|
||||||
|
drawSimpleWeightChart(doc, weights, bird.chart_color, page.margin, y, contentWidth, 120);
|
||||||
|
y += 140;
|
||||||
|
|
||||||
|
y = drawSectionTitle(doc, 'Weight History', y);
|
||||||
|
y = drawTable(
|
||||||
|
doc,
|
||||||
|
['Date', 'Weight', 'Notes'],
|
||||||
|
weights.length ? weights.map((entry) => [formatDate(entry.recorded_on), formatWeight(entry.weight_grams), entry.notes || '']) : [['No weights recorded.', '', '']],
|
||||||
|
page.margin,
|
||||||
|
y,
|
||||||
|
[95, 70, contentWidth - 165],
|
||||||
|
24,
|
||||||
|
);
|
||||||
|
|
||||||
if (notes.length) {
|
if (notes.length) {
|
||||||
if (y > 635) {
|
if (y > 635) {
|
||||||
doc.addPage();
|
doc.addPage();
|
||||||
|
|||||||
Reference in New Issue
Block a user