Skip to content

Commit

Permalink
Merge branch 'master' into chore/use-paths-data
Browse files Browse the repository at this point in the history
  • Loading branch information
terotik committed Dec 12, 2024
2 parents 6913340 + 20a547a commit cda9ec5
Show file tree
Hide file tree
Showing 11 changed files with 56 additions and 629 deletions.
10 changes: 5 additions & 5 deletions common/__generated__/graphql.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion components/common/GlobalNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ import NavbarSearch from './NavbarSearch';

const getRootLink = (plan, locale, primaryLanguage) => {
if (plan.parent && plan.parent.viewUrl) {
const shouldAppendLocale = locale !== primaryLanguage;
const shouldAppendLocale =
locale !== primaryLanguage &&
!plan.parent.viewUrl.includes(`/${locale}/`);
if (shouldAppendLocale) {
return `${plan.parent.viewUrl}/${locale}/`;
}
Expand Down
33 changes: 26 additions & 7 deletions components/common/StreamField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import IndicatorShowcaseBlock from 'components/contentblocks/IndicatorShowcaseBl
import QuestionAnswerBlock from 'components/contentblocks/QuestionAnswerBlock';
import RelatedIndicatorsBlock from 'components/contentblocks/RelatedIndicatorsBlock';
import RelatedPlanListBlock from 'components/contentblocks/RelatedPlanListBlock';
import { ImageCredit } from 'components/contentblocks/ContentPageHeaderBlock';
import { useTranslations } from 'next-intl';
import { usePlan } from 'context/plan';
import { Col, ColProps, Container, Row } from 'reactstrap';
import { ColumnProps } from 'reactstrap/types/lib/Col';
Expand Down Expand Up @@ -124,6 +126,7 @@ function StreamFieldBlock(props: StreamFieldBlockProps) {
const { __typename } = block;
const plan = usePlan();
const theme = useTheme();
const t = useTranslations();

switch (__typename) {
case 'RichTextBlock': {
Expand Down Expand Up @@ -255,6 +258,7 @@ function StreamFieldBlock(props: StreamFieldBlockProps) {
* fit_to_column: image is limited to text block width
* Image keeps it original ratio and doesn't crop
*/

const getColSize = (breakpoint) => {
if (block.width === 'maximum') return {};
switch (breakpoint) {
Expand All @@ -275,16 +279,31 @@ function StreamFieldBlock(props: StreamFieldBlockProps) {
xl={getColSize('xl')}
lg={getColSize('lg')}
md={getColSize('md')}
style={{
position: 'relative',
}}
>
<img
src={block.image?.renditionUncropped?.src}
alt={block.image?.altText}
<div
style={{
display: 'block',
width: '100%',
marginBottom: theme.spaces.s600,
position: 'relative',
display: 'inline-block',
}}
/>
>
<img
src={block.image?.renditionUncropped?.src}
alt={block.image?.altText}
style={{
display: 'block',
width: '100%',
marginBottom: theme.spaces.s600,
}}
/>
{block.image?.imageCredit && (
<ImageCredit>
{`${t('image-credit')}: ${block.image.imageCredit}`}
</ImageCredit>
)}
</div>
</Col>
</Row>
</Container>
Expand Down
2 changes: 1 addition & 1 deletion components/contentblocks/ContentPageHeaderBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const ContentHeader = styled.header`
}
`;

const ImageCredit = styled.span`
export const ImageCredit = styled.span`
position: absolute;
top: 0;
right: 0;
Expand Down
187 changes: 5 additions & 182 deletions components/dashboard/ActionStatusExport.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { getActionTaskTermContext, getActionTermContext } from 'common/i18n';
import { cleanActionStatus } from 'common/preprocess';
import { usePlan } from 'context/plan';
import { useTranslations } from 'next-intl';
import {
Expand All @@ -9,193 +7,18 @@ import {
DropdownItem,
} from 'reactstrap';

async function exportActions(
t,
actions,
actionStatuses,
plan,
fileFormat = 'excel'
) {
const Excel = (await import('exceljs')).default;
const fileSaver = (await import('file-saver')).default;
const workbook = new Excel.Workbook();
const worksheet = workbook.addWorksheet(
t('actions', getActionTermContext(plan))
);
const planIds = new Set(actions.map((action) => action.plan.id));
const multiplePlans = planIds.size > 1;
const columns = [
{ header: t('action-identifier'), key: 'id', width: 10 },
{
header: t('action-name-title', getActionTermContext(plan)),
key: 'name',
width: 50,
},
{ header: t('status'), key: 'status', width: 20 },
{
header: t('action-implementation-phase'),
key: 'implementationPhase',
width: 20,
},
{ header: t('action-last-updated'), key: 'lastUpdated', width: 15 },
{
header: t('tasks-on-time', getActionTaskTermContext(plan)),
key: 'ontimeTasks',
width: 10,
},
{
header: t('tasks-late', getActionTaskTermContext(plan)),
key: 'lateTasks',
width: 10,
},
{
header: t('tasks-completed', getActionTaskTermContext(plan)),
key: 'completedTasks',
width: 10,
},
{
header: t('action-tasks', getActionTaskTermContext(plan)),
key: 'tasks',
width: 10,
},
{
header: t('responsible-organizations-primary'),
key: 'primaryResponsibleOrgs',
width: 20,
},
{
header: t('responsible-organizations-collaborator'),
key: 'collaboratorResponsibleOrgs',
width: 20,
},
{
header: t('responsible-organizations-other'),
key: 'otherResponsibleOrgs',
width: 20,
},
];
if (multiplePlans) {
columns.unshift({
header: t('plan'),
key: 'plan',
width: 20,
});
}
// When assigning to worksheet.columns, some magic happens and manipulating worksheet.columns afterwards does not
// yield the desired results. So we prepare the columns array separately before assigning it to worksheet.columns.
worksheet.columns = columns;
actions.forEach((act) => {
const status = cleanActionStatus(act, actionStatuses);
// Remove any soft hyphens in action name (due to `hyphenated: true` when querying the name) as Excel renders
// visible hyphens instead.
const actionName = act.name?.replaceAll('­', '');
let activePhaseName = act.implementationPhase?.name;
if (status != null) {
// FIXME: Duplicated logic from ActionPhase.js
const inactive = [
'cancelled',
'merged',
'postponed',
'completed',
].includes(status.identifier);
if (inactive) activePhaseName = status.name;
}

const tasks = act.tasks;
let tasksCount = tasks.length;
// FIXME: Duplicated logic from ActionStatusTable.js
let ontimeTasks = 0;
let lateTasks = 0;
let completedTasks = 0;
const nowDate = new Date();

tasks.forEach((task) => {
const taskDue = new Date(task.dueAt);
switch (task.state) {
case 'NOT_STARTED':
case 'IN_PROGRESS':
if (taskDue < nowDate) lateTasks += 1;
else ontimeTasks += 1;
break;
case 'COMPLETED':
completedTasks += 1;
break;
default:
tasksCount -= 1;
}
});

const getOrgName = ({ organization }) => organization.name;

const parties = act.responsibleParties;
const primaryResponsibleOrgs = parties
.filter((p) => p.role === 'PRIMARY')
.map(getOrgName);
const collaboratorResponsibleOrgs = parties
.filter((p) => p.role === 'COLLABORATOR')
.map(getOrgName);
const otherResponsibleOrgs = parties
.filter((p) => p.role === null)
.map(getOrgName);

const row = [
act.identifier,
actionName,
status?.name,
activePhaseName,
new Date(act.updatedAt),
ontimeTasks,
lateTasks,
completedTasks,
tasksCount,
primaryResponsibleOrgs.join(';'),
collaboratorResponsibleOrgs.join(';'),
otherResponsibleOrgs.join(';'),
];
if (multiplePlans) {
row.unshift(act.plan.name);
}
worksheet.addRow(row);
});

const today = new Date().toISOString().split('T')[0];
switch (fileFormat) {
case 'excel':
const xls64 = await workbook.xlsx.writeBuffer({ base64: true });
fileSaver.saveAs(
new Blob([xls64], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
}),
`${t('actions', getActionTermContext(plan))}-${today}.xlsx`
);
break;

case 'csv':
const csv64 = await workbook.csv.writeBuffer({ base64: true });
fileSaver.saveAs(
new Blob([csv64], { type: 'text/csv' }),
`${t('actions', getActionTermContext(plan))}-${today}.csv`
);
break;

default:
throw new Error('Unknown file format');
}
}

export default function ActionStatusExport({ actions }) {
const t = useTranslations();
const plan = usePlan();
const { actionStatuses } = plan;
const handleExport = async (format) => {
await exportActions(t, actions, actionStatuses, plan, format);
};
const url = plan.actionReportExportViewUrl;
const csvExportUrl = `${url}?format=csv`;
const excelExportUrl = `${url}?format=xlsx`;
return (
<UncontrolledDropdown>
<DropdownToggle caret>{t('export')}</DropdownToggle>
<DropdownMenu>
<DropdownItem onClick={() => handleExport('excel')}>Excel</DropdownItem>
<DropdownItem onClick={() => handleExport('csv')}>CSV</DropdownItem>
<DropdownItem href={excelExportUrl}>Excel</DropdownItem>
<DropdownItem href={csvExportUrl}>CSV</DropdownItem>
</DropdownMenu>
</UncontrolledDropdown>
);
Expand Down
6 changes: 0 additions & 6 deletions components/dashboard/dashboard.constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,6 @@ export const COLUMN_CONFIG: { [key in ColumnBlock]: Column } = {
renderCell: (action) => action.identifier,
},

// TODO: Remove from the backend
ImpactColumnBlock: {
renderHeader: () => '',
renderCell: () => '',
},

NameColumnBlock: {
sortable: true,
headerKey: 'name',
Expand Down
3 changes: 0 additions & 3 deletions fragments/action-list.fragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,6 @@ export const ACTION_TABLE_COLUMN_FRAGMENT = gql`
... on OrganizationColumnBlock {
columnLabel
}
... on ImpactColumnBlock {
columnLabel
}
... on FieldColumnBlock {
columnLabel
field
Expand Down
1 change: 1 addition & 0 deletions fragments/stream-field.fragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export const STREAM_FIELD_FRAGMENT = gql`
renditionUncropped: rendition(size: "1320x1320", crop: false) {
src
}
imageCredit
}
width
}
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@
"dotenv": "^16.0.0",
"echarts": "^5.5.1",
"escape-string-regexp": "^5.0.0",
"exceljs": "^4.3.0",
"express-robots-txt": "^1.0.0",
"file-saver": "^2.0.5",
"fontfaceobserver": "^2.1.0",
"framer-motion": "^11.0.24",
"html-react-parser": "^1.4.12",
Expand Down
1 change: 1 addition & 0 deletions queries/get-plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const GET_PLAN_CONTEXT = gql`
publishedAt
kausalPathsInstanceUuid
viewUrl(clientUrl: $clientUrl)
actionReportExportViewUrl
primaryActionClassification {
id
identifier
Expand Down
Loading

0 comments on commit cda9ec5

Please sign in to comment.