Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions app/components/AdditionalLinkList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function AdditionalLinkList({ linkType }: LinkListProps) {
},
});

const items = data?.publicLinks?.results ?? [];
const links = data?.publicLinks?.results ?? [];

return (
<Container
Expand All @@ -76,26 +76,29 @@ function AdditionalLinkList({ linkType }: LinkListProps) {
onActivePageChange={setPage}
/>
)}
empty={links.length === 0}
emptyMessage={`No ${linkType.toLowerCase()} links found.`}
>
<ListView
layout="block"
spacing="2xl"
>
{items.map((item) => (
{links.map((link) => (
<Container
key={item.id}
key={link.id}
spacingOffset={-2}
headingLevel={4}
heading={item.title}
headerDescription={item.description}
heading={link.title}
headerDescription={link.description}

>
<Link
href={item.url}
href={link.url}
external
styleVariant="action"
colorVariant="primary"
>
{item.url}
{link.url}
</Link>
</Container>
))}
Expand Down
1 change: 1 addition & 0 deletions app/components/DocumentList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ function DocumentList(props: Props) {
<Container
pending={fetching}
empty={data?.reports.totalCount === 0}
emptyMessage={`No documents found for the ${reportType.toLowerCase()}.`}
>
<ListView
layout="grid"
Expand Down
63 changes: 63 additions & 0 deletions app/components/DocumentViewer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import '@cyntler/react-doc-viewer/dist/index.css';

import { useMemo } from 'react';
import DocViewer, {
DocViewerRenderers,
type IConfig,
} from '@cyntler/react-doc-viewer';
import { ErrorWarningFillIcon } from '@ifrc-go/icons';
import { Message } from '@ifrc-go/ui';

import useAuth from '#hooks/useAuth';

import styles from './styles.module.css';

function NoRendererMessage() {
const { isAuthenticated } = useAuth();
return (
<Message
className={styles.noRendererMessage}
variant="error"
icon={<ErrorWarningFillIcon />}
title="Preview not available"
description={`This file type cannot be previewed. ${isAuthenticated ? 'Please download the file to view it.' : ''}`}
/>
);
}

const viewerConfig: IConfig = {
header: {
disableHeader: true,
disableFileName: true,
},
pdfVerticalScrollByDefault: true,
noRenderer: {
overrideComponent: NoRendererMessage,
},
};

interface DocumentViewerProps {
fileUrl: string;
fileName?: string;
}

function DocumentViewer({
fileUrl,
fileName,
}: DocumentViewerProps) {
const documents = useMemo(
() => [{ uri: fileUrl, fileName }],
[fileUrl, fileName],
);
return (
<div className={styles.DocumentViewer}>
<DocViewer
documents={documents}
pluginRenderers={DocViewerRenderers}
config={viewerConfig}
/>
</div>
);
}

export default DocumentViewer;
25 changes: 25 additions & 0 deletions app/components/DocumentViewer/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@


/* The pdf renderer's own floating bottom toolbar, pagination bar, and per-page page-number overlay */
:global(#pdf-controls),
:global(#pdf-pagination),
:global(#pdf-page-info) {
display: none;
}

/*
* docx/doc/xls/ppt files render through an iframe to the MS Office Online
* viewer. Its parent has no defined height, so its own `height: 100%` falls
* back to the ~150px iframe default, forcing the office viewer to scroll
* internally. Give it a tall, explicit height so the full document renders
* and the outer page scrolls instead.
*/
:global(#msdoc-renderer),
:global(#msdoc-iframe) {
height: 112vh;
}

.no-renderer-message {
aspect-ratio: 5 / 3;
color: var(--go-ui-color-text-light);
}
75 changes: 0 additions & 75 deletions app/components/PdfViewer/index.tsx

This file was deleted.

77 changes: 66 additions & 11 deletions app/components/PowerBiEmbed/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,53 @@
/* eslint-disable no-console */
import {
useMemo,
useState,
} from 'react';
import { isFalsyString } from '@togglecorp/fujs';
import type { IReportEmbedConfiguration } from 'powerbi-client';
import { models } from 'powerbi-client';
import { PowerBIEmbed as PowerBI } from 'powerbi-client-react';
import type { ICustomEvent } from 'service';

import styles from './styles.module.css';

function PowerBIEmbed({ embedUrl }: { embedUrl: string }) {
const embedConfig: IReportEmbedConfiguration = {
const VALID_EMBED_HOSTNAME_SUFFIXES = [
'powerbi.com',
'powerbigov.us',
'powerbi.cn',
];

function isValidEmbedUrl(embedUrl: string) {
if (isFalsyString(embedUrl)) {
return false;
}

try {
const parsed = new URL(embedUrl);
return parsed.protocol === 'https:'
&& VALID_EMBED_HOSTNAME_SUFFIXES.some(
(suffix) => parsed.hostname === suffix || parsed.hostname.endsWith(`.${suffix}`),
);
} catch {
return false;
}
}

interface Props {
embedUrl: string;
}

function PowerBIEmbed(props: Props) {
const { embedUrl } = props;

// Stores the last embedUrl that failed to load, so that passing in a
// different (or corrected) url is treated as a fresh attempt.
const [erroredUrl, setErroredUrl] = useState<string>();

const embedConfig: IReportEmbedConfiguration = useMemo(() => ({
type: 'report',
embedUrl,
// id/accessToken are unused for this public, token-less embed flow,
// but the type requires the keys to be present.
id: undefined,
accessToken: undefined,
tokenType: models.TokenType.Embed,
Expand All @@ -23,19 +61,36 @@ function PowerBIEmbed({ embedUrl }: { embedUrl: string }) {
},
navContentPaneEnabled: true,
},
};
}), [embedUrl]);

const eventHandlers = useMemo(() => new Map([
// powerbi-client-react expects a Map of PowerBI event names to
// handlers; 'error' is the only one we currently need.
['error', (event?: ICustomEvent<models.IError>) => {
// eslint-disable-next-line no-console
console.error('PowerBI embed error:', event?.detail);
setErroredUrl(embedUrl);
}],
]), [embedUrl]);

// Comparing against embedUrl (rather than a plain boolean) lets a new/corrected
// url reset the error state automatically, without a useEffect.
const hasPreviouslyErrored = erroredUrl === embedUrl;
const canEmbed = isValidEmbedUrl(embedUrl) && !hasPreviouslyErrored;

if (!canEmbed) {
return (
<div className={styles.embedError}>
Unable to load this dashboard. The embed link is missing or invalid.
</div>
);
}

return (
<PowerBI
embedConfig={embedConfig}
cssClassName={styles.embed}
eventHandlers={
new Map([
['loaded', () => console.log('Report loaded')],
['rendered', () => console.log('Report rendered')],
['error', (event?: ICustomEvent<models.IError>) => console.error('Error:', event?.detail)],
])
}
eventHandlers={eventHandlers}
/>
);
}
Expand Down
11 changes: 11 additions & 0 deletions app/components/PowerBiEmbed/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,14 @@
border: none;
}
}

.embed-error {
display: flex;
align-items: center;
justify-content: center;
aspect-ratio: 5 / 3;
background-color: var(--go-ui-color-background);
padding: 1rem;
text-align: center;
color: var(--go-ui-color-text-light);
}
1 change: 1 addition & 0 deletions app/components/ReportCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ function ReportCard({ report }: ReportCardProps) {
</Heading>
<Description
withLightText
className={styles.description}
>
{description}
</Description>
Expand Down
9 changes: 9 additions & 0 deletions app/components/ReportCard/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,12 @@
}
}

.description {
display: -webkit-box;
overflow: hidden;
line-height: var(--go-ui-line-height-md);
-webkit-box-orient: vertical;
-webkit-line-clamp: 5;
line-clamp: 5;
}

Loading
Loading