WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 28629c9

Browse files
authored
Merge pull request #64 from liamgold/feature/24-export-pdf
feat: Add PDF export functionality for sustainability reports
2 parents eb8ba85 + fd4ea17 commit 28629c9

15 files changed

+375
-12
lines changed

.claude/agents/xbyk-component-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: xbyk-component-reference
33
description: Quick reference guide for native Xperience by Kentico admin components. Consult when choosing components, understanding XbyK design system, or ensuring consistent UI patterns.
44
tools: Read, mcp__kentico-docs-mcp__kentico_docs_search, mcp__kentico-docs-mcp__kentico_docs_fetch
55
model: haiku
6-
color: teal
6+
color: pink
77
---
88

99
You are a quick reference guide for native Xperience by Kentico (XbyK) admin UI components from `@kentico/xperience-admin-components`.

CLAUDE.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ C:\Projects\xperience-community-sustainability\
167167

168168
**XbyK Components Used**:
169169
- Native: `Card`, `Button`, `Stack`, `Row`, `Column`, `Headline`, `Spacing`, `Icon`
170-
- Patterns: `usePageCommand` hook for backend commands, `inProgress` prop for loading states
170+
- Patterns: `usePageCommand` hook for backend commands, manual `useState` for loading states, `inProgress` and `disabled` props on buttons
171171
- Responsive: `colsLg`/`colsMd` breakpoints for grid layout
172172

173173
**File Organization**:
@@ -190,6 +190,46 @@ C:\Projects\xperience-community-sustainability\
190190
}
191191
```
192192

193+
**usePageCommand Hook - Loading State Pattern**:
194+
- **IMPORTANT**: `usePageCommand` does NOT provide a built-in `inProgress` property
195+
- **Return type**: Only returns `{ execute: (data?: TCommandData) => void }`
196+
- **Loading states must be managed manually** using `useState`
197+
- **Correct pattern**:
198+
```typescript
199+
// 1. Create manual state
200+
const [isLoading, setIsLoading] = useState(false);
201+
202+
// 2. Configure usePageCommand with after/onError callbacks
203+
const { execute: myCommand } = usePageCommand<ResponseType>(
204+
Commands.MyCommand,
205+
{
206+
after: (response) => {
207+
// Process response...
208+
setIsLoading(false); // Stop loading when done
209+
},
210+
onError: (err) => {
211+
console.error(err);
212+
setIsLoading(false); // Stop loading on error
213+
},
214+
}
215+
);
216+
217+
// 3. Set loading state before executing
218+
const handleClick = () => {
219+
setIsLoading(true);
220+
myCommand();
221+
};
222+
223+
// 4. Use state for button props
224+
<Button
225+
label="Run Command"
226+
onClick={handleClick}
227+
disabled={isLoading}
228+
inProgress={isLoading}
229+
/>
230+
```
231+
- **Examples in codebase**: See `isLoading` (line 26), `isExportingPdf` (line 27), and `isLoadingMore` (line 38) in SustainabilityTabTemplate.tsx
232+
193233
### 4. JavaScript Analysis (src/wwwroot/scripts/resource-checker.js)
194234

195235
**Process**:

README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,6 @@ A community-driven open-source package that brings sustainability insights and a
1212

1313
> For more details about this package, check out [Bringing Sustainability Insights to Xperience by Kentico](https://www.goldfinch.me/blog/bringing-sustainability-insights-to-xperience-by-kentico) which provides more background information around the package and its origin.
1414
15-
## Screenshots
16-
17-
Once installed, a new tab appears for each page in your web channels. The Sustainability tab allows content editors and marketers to see and benchmark page weight and carbon emissions, which is then converted to a carbon rating for individual pages.
18-
19-
<a href="/src/images/SustainabilityReport-PageTab.jpeg">
20-
<img src="/src/images/SustainabilityReport-PageTab.jpeg" width="800" alt="Sustainability Tab for pages in Xperience by Kentico">
21-
</a>
22-
2315
## Library Version Matrix
2416

2517
| Xperience Version | Library Version |
@@ -83,18 +75,29 @@ The hosting status is displayed in the Sustainability report with three possible
8375

8476
### Sustainability Report
8577

78+
Once installed, a new tab appears for each page in your web channels. The Sustainability tab allows content editors and marketers to see and benchmark page weight and carbon emissions, which is then converted to a carbon rating for individual pages.
79+
80+
<a href="/src/images/SustainabilityReport-PageTab.jpeg">
81+
<img src="/src/images/SustainabilityReport-PageTab.jpeg" width="800" alt="Sustainability Tab for pages in Xperience by Kentico">
82+
</a>
83+
8684
Each report includes:
8785

8886
- **Carbon Rating**: Letter grade (A+ through F) based on grams CO₂ per page view
8987
- **CO₂ Emissions**: Total carbon emissions with hosting status indicator
9088
- **Page Weight**: Total size of all resources loaded
9189
- **Resource Breakdown**: Categorized by type (Images, Scripts, CSS, etc.) with individual file sizes
9290
- **Optimization Tips**: Xperience-specific recommendations for reducing page weight
91+
- **PDF Export**: Download complete sustainability reports as PDF for sharing and documentation
9392

9493
### Historical Tracking
9594

9695
Track sustainability improvements over time with comprehensive historical reporting:
9796

97+
<a href="/src/images/SustainabilityReport-ReportHistory.jpeg">
98+
<img src="/src/images/SustainabilityReport-ReportHistory.jpeg" width="800" alt="Historical tracking with trend charts and report history">
99+
</a>
100+
98101
- **Trend Analysis**: Two side-by-side charts showing CO₂ emissions and page weight trends over time
99102
- **Report History**: View all previous sustainability reports for a page with collapsible cards showing key metrics
100103
- **Pagination**: Load historical reports in batches to efficiently browse through your sustainability history

src/Client/dist/entry.kxh.2f8ca3a4baf4f24f2fb3.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Client/dist/entry.kxh.d81750fa880d7e2c5f70.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/Client/src/Sustainability/tab-template/SustainabilityTabTemplate.tsx

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const SustainabilityTabTemplate = (
2424
props: SustainabilityTabTemplateProps | null
2525
) => {
2626
const [isLoading, setIsLoading] = useState(false);
27+
const [isExportingPdf, setIsExportingPdf] = useState(false);
2728
const [error, setError] = useState<string | null>(null);
2829
const [data, setData] = useState<SustainabilityData | undefined | null>(
2930
props?.sustainabilityData
@@ -36,7 +37,9 @@ export const SustainabilityTabTemplate = (
3637
);
3738
const [isLoadingMore, setIsLoadingMore] = useState(false);
3839
// Initialize hasMoreHistory from backend
39-
const [hasMoreHistory, setHasMoreHistory] = useState(props?.hasMoreHistory ?? false);
40+
const [hasMoreHistory, setHasMoreHistory] = useState(
41+
props?.hasMoreHistory ?? false
42+
);
4043
const [showHistory, setShowHistory] = useState(false);
4144
const [nextPageIndex, setNextPageIndex] = useState(1); // Initial load got page 0, next is page 1
4245

@@ -90,6 +93,38 @@ export const SustainabilityTabTemplate = (
9093
},
9194
});
9295

96+
const exportPdf = usePageCommand<{ pdfBase64: string; fileName: string }>(
97+
Commands.ExportReportAsPdf,
98+
{
99+
after: (response) => {
100+
if (response) {
101+
// Convert base64 to blob and trigger download
102+
const byteCharacters = atob(response.pdfBase64);
103+
const byteArray = new Uint8Array(byteCharacters.length);
104+
for (let i = 0; i < byteCharacters.length; i++) {
105+
byteArray[i] = byteCharacters.charCodeAt(i);
106+
}
107+
const blob = new Blob([byteArray], { type: "application/pdf" });
108+
109+
// Create download link
110+
const url = window.URL.createObjectURL(blob);
111+
const link = document.createElement("a");
112+
link.href = url;
113+
link.download = response.fileName;
114+
document.body.appendChild(link);
115+
link.click();
116+
document.body.removeChild(link);
117+
window.URL.revokeObjectURL(url);
118+
}
119+
setIsExportingPdf(false);
120+
},
121+
onError: (err) => {
122+
console.error("PDF export error:", err);
123+
setIsExportingPdf(false);
124+
},
125+
}
126+
);
127+
93128
if (data === undefined || data === null) {
94129
return (
95130
<div style={{ padding: "32px", maxWidth: "1400px", margin: "0 auto" }}>
@@ -198,22 +233,37 @@ export const SustainabilityTabTemplate = (
198233
label="Back to Current Report"
199234
color={ButtonColor.Secondary}
200235
size={ButtonSize.M}
236+
icon="xp-arrow-left"
201237
onClick={() => setShowHistory(false)}
202238
/>
203239
) : (
204240
<>
241+
<Button
242+
label="Export as PDF"
243+
color={ButtonColor.Secondary}
244+
size={ButtonSize.M}
245+
icon="xp-file-pdf"
246+
disabled={isExportingPdf}
247+
inProgress={isExportingPdf}
248+
onClick={() => {
249+
setIsExportingPdf(true);
250+
exportPdf.execute();
251+
}}
252+
/>
205253
{historicalReports.length > 0 && (
206254
<Button
207255
label="View History"
208256
color={ButtonColor.Secondary}
209257
size={ButtonSize.M}
258+
icon="xp-clock"
210259
onClick={() => setShowHistory(true)}
211260
/>
212261
)}
213262
<Button
214263
label="Run New Analysis"
215264
color={ButtonColor.Primary}
216265
size={ButtonSize.M}
266+
icon="xp-rotate-right"
217267
disabled={isLoading}
218268
inProgress={isLoading}
219269
onClick={() => {

src/Client/src/Sustainability/tab-template/current/ResourceGroupCard.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ export const ResourceGroupCard = ({ group, totalPageSize }: ResourceGroupCardPro
190190
<div style={resourceGroupCardStyles.expandButtonContainer}>
191191
<Button
192192
label={expanded ? "Show less" : `Show ${group.resources.length - 3} more`}
193+
trailingIcon={expanded ? "xp-chevron-up" : "xp-chevron-down"}
193194
onClick={() => setExpanded(!expanded)}
194195
color={ButtonColor.Secondary}
195196
size={ButtonSize.S}

src/Client/src/Sustainability/tab-template/history/HistoryView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export const HistoryView = ({
4949
label="Load More History"
5050
color={ButtonColor.Secondary}
5151
size={ButtonSize.M}
52+
trailingIcon="xp-chevron-down"
5253
disabled={isLoadingMore}
5354
inProgress={isLoadingMore}
5455
onClick={() => onLoadMore(nextPageIndex)}

src/Client/src/Sustainability/tab-template/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@ export interface SustainabilityTabTemplateProps {
3535
export const Commands = {
3636
RunReport: "RunReport",
3737
LoadMoreHistory: "LoadMoreHistory",
38+
ExportReportAsPdf: "ExportReportAsPdf",
3839
};

src/Extensions/SustainabilityServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public static IServiceCollection AddXperienceCommunitySustainability(this IServi
1818
services.Configure<SustainabilityOptions>(configuration.GetSection("Sustainability"));
1919

2020
services.AddScoped<ISustainabilityService, SustainabilityService>();
21+
services.AddScoped<ISustainabilityPdfService, SustainabilityPdfService>();
2122
services.AddScoped<IContentHubLinkService, ContentHubLinkService>();
2223
services.AddSingleton<ISustainabilityModuleInstaller, SustainabilityModuleInstaller>();
2324

0 commit comments

Comments
 (0)