Skip to content

Source Report

Source Report

Cette page est générée automatiquement à partir du dépôt local au moment de la génération de la documentation.

Fichiers inclus

packages/common/src/lib/widgets/export/report/report.declaration.ts

packages/common/src/lib/widgets/export/report/report.declaration.ts
import { widgetFactorySvelte, type WidgetProps } from '$lib/api/managers/widget';
import { type ReportConfig, reportConfigSchema } from './report.config';
import type { WidgetDeclaration } from '$lib/api/managers/widget/widget-declaration';
export const declaration = {
factory: () => import('./Report.svelte').then((Report) => widgetFactorySvelte(Report)),
schema: () => reportConfigSchema,
} satisfies WidgetDeclaration;
export type ReportProps = WidgetProps<ReportConfig>;

packages/common/src/lib/widgets/export/report/report.config.ts

packages/common/src/lib/widgets/export/report/report.config.ts
import { defineWidgetConfig, inToolbarSchemaFrom } from '$lib/api/managers/configuration';
import { type I18nRegistry, i18nSchemaFrom } from '$lib/api/managers/i18n';
import { z } from 'zod';
import { SelectionType } from '$lib/api/tools';
import { drawFeatureSymbolsSchema, simplePointSymbolSchema } from '$lib/api/symbol';
export const reportTranslation = {
'report-zone': {
fr: 'Zone du rapport',
nl: 'NL - Zone du rapport',
},
'selection-explanation': {
fr: 'Cliquez sur la carte pour déterminer la zone du rapport',
nl: 'NL - Cliquez sur la carte pour déterminer la zone du rapport',
},
'report-generation': {
fr: 'Le rapport est en cours de création. Veuillez patienter',
nl: 'NL - Le rapport est en cours de création. Veuillez patienter',
},
title: {
fr: 'Titre',
nl: 'NL - Titre',
},
description: {
fr: 'Description',
nl: 'NL - Description',
},
generate: {
fr: 'Générer le rapport',
nl: 'NL - Générer le rapport',
},
'no-draw': {
fr: "Veuillez d'abord dessiner sur la carte",
nl: "NL - Veuillez d'abord dessiner sur la carte",
},
'no-parcel-found': {
fr: 'Pas de parcelle trouvée sous ce point',
nl: 'NL - Pas de parcelle trouvée sous ce point',
},
POINT: {
fr: 'Point',
nl: 'NL - Point',
},
LINE: {
fr: 'Ligne',
nl: 'NL - Ligne',
},
POLYGON: {
fr: 'Polygone',
nl: 'NL - Polygone',
},
CADMAP: {
fr: 'Parcelle cadastrale',
nl: 'NL - Parcelle cadastrale',
},
} satisfies I18nRegistry;
export const reportConfigSchema = defineWidgetConfig({
i18n: i18nSchemaFrom(reportTranslation),
title: {
fr: 'Créer un rapport',
nl: 'NL - Créer un rapport',
},
icon: {
lucide: 'ClipboardPlus',
},
inToolbar: inToolbarSchemaFrom({
type: 'button',
}),
onActivate: {
deactivate: {
classes: ['Identify', 'AddData', 'Draw', 'MeasureDistance', 'MeasureSurface', 'AdvancedSearch', 'Export'],
},
},
config: z
.object({
selectionTypes: z
.array(z.enum(SelectionType))
.default([SelectionType.POINT, SelectionType.LINE, SelectionType.POLYGON, SelectionType.CADMAP]),
templateName: z.string().default('REPORT_VERTICAL_PDF'),
templateApiUrl: z.string().default('https://geoservices.test.wallonie.be/geoviewer-services/api/template'),
identifyTolerance: z.number().default(10),
closestAddressBuffer: z.number().default(10),
defaultSymbols: drawFeatureSymbolsSchema.prefault({}),
})
.prefault({}),
});
export type ReportConfig = z.infer<typeof reportConfigSchema>;

packages/common/src/lib/widgets/export/report/report.models.ts

packages/common/src/lib/widgets/export/report/report.models.ts
import type { Icon } from '$lib/api/icons';
import { type DrawCreateType, SelectionType } from '$lib/api/tools';
export const selectionToIcon: Record<SelectionType, Icon> = {
[SelectionType.POINT]: {
lucide: 'Dot',
},
[SelectionType.LINE]: {
lucide: 'Waypoints',
},
[SelectionType.POLYGON]: {
geoviewer: 'geoviewer-polygon',
},
[SelectionType.CADMAP]: {
lucide: 'MapPinned',
},
};
export const selectionToDrawType: Record<SelectionType, DrawCreateType> = {
[SelectionType.POINT]: 'point',
[SelectionType.CADMAP]: 'point',
[SelectionType.LINE]: 'polyline',
[SelectionType.POLYGON]: 'polygon',
};

packages/common/src/lib/widgets/export/report/Report.svelte

packages/common/src/lib/widgets/export/report/Report.svelte
<script lang="ts">
import { Button } from '$lib/components/shadcn/ui/button';
import { getMapManager } from '$lib/api/map';
import { type GenerateReportParams, SelectionType } from '$lib/api/tools';
import { getI18n } from '$lib/api/managers/i18n';
import BlockingLoader from '$lib/components/blocking-loader/BlockingLoader.svelte';
import { selectionToDrawType, selectionToIcon } from './report.models';
import { Icon } from '$lib/components/icon';
import { Root as TabsRoot, TabsList, TabsTrigger } from '$lib/components/shadcn/ui/tabs';
import { cn } from '$lib/components/shadcn/utils';
import { ExternalLink } from 'lucide-svelte';
import { initGraphicMapServiceConfiguration } from '$lib/api/managers/configuration';
import { onDestroy } from 'svelte';
import { GeoviewerError } from '$lib/api/managers/error/geoviewer-error.model';
import { reverse } from '$lib/api/utils';
import { LegendNode } from '$lib/api/utils/legend/legend.model.svelte';
import { showToast } from '$lib/components/toast/toast.utils';
import { getDefaultTemplateParams, getTitle } from '$lib/api/tools/report/report.utils';
import type { ReportProps } from './report.declaration';
import type { ApiFeature, ApiFeaturePoint } from '$lib/api/feature';
import type { ParcelResult } from '$lib/api/services';
import { type DrawFeatureSymbols, simplePointSymbolSchema } from '$lib/api/symbol';
import { Input } from '$lib/components/shadcn/ui/input';
import { Textarea } from '$lib/components/shadcn/ui/textarea';
let { fullConfig }: ReportProps = $props();
const { config } = fullConfig;
const mapManager = getMapManager();
const drawFactory = mapManager.tools.draw;
const report = mapManager.tools.report;
const highlight = mapManager.tools.highlight;
const i18n = getI18n(fullConfig.i18n);
const disableGenerateReport = $derived.by(
() => !currentDraw || (currentSelection === SelectionType.CADMAP && !currentCadFeature) || loadingParcel,
);
const legendNodes = $derived.by(() =>
reverse(mapManager.layerList.list)
.filter((x) => x.toc.visible && x.visible)
.map((service) => new LegendNode(service)),
);
const serviceIdentifier = 'ReportLayerId';
const graphicMapService = mapManager.addGraphicMapService(
initGraphicMapServiceConfiguration({
label: 'ReportLayerDrawService',
id: serviceIdentifier,
toc: {
visible: false,
},
}),
);
const invisiblePointSymbol = simplePointSymbolSchema.parse({
...config.defaultSymbols.point,
size: 0,
});
const defaultSymbolWithInvisiblePoint: DrawFeatureSymbols = {
...config.defaultSymbols,
point: invisiblePointSymbol,
};
const drawTool = drawFactory.create({ layer: graphicMapService, defaultSymbols: defaultSymbolWithInvisiblePoint });
const currentDrawType = $derived.by(() => selectionToDrawType[currentSelection]);
let templateParams = $state(getDefaultTemplateParams(config.templateName, config.templateApiUrl));
let loading = $state<boolean>(false);
let loadingParcel = $state<boolean>(false);
let currentSelection = $state<SelectionType>(SelectionType.POINT);
let currentDraw = $state<ApiFeature | undefined>();
let currentCadFeature = $state<ApiFeature | undefined>();
let currentParcelResult: ParcelResult | undefined;
let abortController: AbortController | null = null;
onDestroy(() => abortController?.abort());
$effect(() => {
if (currentSelection) {
activateDraw();
}
});
function drawTypeChanged() {
if (currentDraw) {
drawTool.delete(currentDraw);
}
onParcelResult(undefined);
currentParcelResult = undefined;
}
function activateDraw() {
drawTool.create({ type: currentDrawType, onDrawComplete });
}
function deactivateDraw() {
drawTool.stop();
}
async function onDrawComplete(feature: ApiFeature) {
if (currentDraw) {
drawTool.delete(currentDraw);
}
templateParams = getDefaultTemplateParams(config.templateName, config.templateApiUrl);
currentDraw = feature;
if (feature.type === 'point') {
feature.symbol = config.defaultSymbols.point;
}
if (currentSelection === SelectionType.CADMAP && currentDraw.type === 'point') {
await findParcelUnderneathClickedPoint(currentDraw);
} else {
onParcelResult(undefined);
}
activateDraw();
}
async function findParcelUnderneathClickedPoint(currentDraw: ApiFeaturePoint) {
loadingParcel = true;
mapManager.services.parcel
.getParcelShapeByPoint(currentDraw)
.then((parcelResult) => {
onParcelResult(parcelResult);
})
.catch(() => {
onParcelResult(undefined);
})
.finally(() => {
loadingParcel = false;
if (currentCadFeature) {
highlight.highlightFeature(currentCadFeature);
drawTool.delete(currentDraw!);
} else {
showToast({ level: 'warning', message: i18n('no-parcel-found') });
}
});
}
function onParcelResult(newParcel: ParcelResult | undefined) {
if (currentCadFeature) {
highlight.unhighlightFeature(currentCadFeature);
}
currentCadFeature = newParcel?.feature;
currentParcelResult = newParcel;
}
async function generateReportClicked() {
loading = true;
const featureToUseForIdentify = currentSelection === SelectionType.CADMAP ? currentCadFeature : currentDraw;
if (!featureToUseForIdentify) {
throw new GeoviewerError(i18n('no-draw'));
}
abortController?.abort();
abortController = new AbortController();
templateParams.fileName = getTitle();
const generateReportParams: GenerateReportParams = {
identifyTolerance: config.identifyTolerance,
closestAddressBuffer: config.closestAddressBuffer,
templateParams,
selectionType: currentSelection,
legendNodes,
selectedParcel: currentParcelResult,
signal: abortController.signal,
};
deactivateDraw();
report
.downloadReport(featureToUseForIdentify, generateReportParams)
.catch((err) => {
if (!abortController?.signal.aborted) {
throw new GeoviewerError(i18n('common.export-error'), { cause: err });
}
})
.finally(() => {
loading = false;
activateDraw();
});
}
function cancelReportGeneration() {
abortController?.abort();
}
activateDraw();
onDestroy(() => {
mapManager.removeMapService(serviceIdentifier);
if (currentCadFeature) {
highlight.unhighlightFeature(currentCadFeature);
}
drawTool.destroy();
});
</script>
<div class="gv-p-4">
<div class="gv-font-bold gv-mb-2">{i18n('report-zone')}</div>
<TabsRoot bind:value={currentSelection} class="gv-w-fit gv-m-auto">
<TabsList>
{#each config.selectionTypes as selection (selection)}
<TabsTrigger
onclick={() => drawTypeChanged()}
disabled={loadingParcel}
value={selection}
class="gv-gap-1.5 gv-font-bold gv-rounded-full"
>
<Icon icon={selectionToIcon[selection]} class="gv-size-4" />
{i18n(selection)}
</TabsTrigger>
{/each}
</TabsList>
</TabsRoot>
<div class="gv-opacity-80 gv-my-2 gv-text-sm">{i18n('selection-explanation')}</div>
<!--TITLE-->
<div class="gv-font-extrabold gv-mb-2">{i18n('title')}</div>
<Input
maxlength={100}
type="text"
data-test-id="Export-title-input"
bind:value={templateParams.title}
class="gv-mb-2"
/>
<!--DESCRIPTION-->
<div class="gv-font-extrabold gv-mb-2">{i18n('description')}</div>
<Textarea
maxlength={500}
data-test-id="Export-description-input"
bind:value={templateParams.description}
rows={3}
class="gv-mb-2"
/>
<Button disabled={disableGenerateReport} class="gv-mt-3" onclick={generateReportClicked} size="sm">
{i18n('generate')}
<ExternalLink class="gv-size-4" />
</Button>
</div>
<BlockingLoader message={i18n('report-generation')} open={loading}>
<div class="gv-flex gv-justify-start">
<Button onclick={cancelReportGeneration} class="gv-border-primary" variant="outline">
{i18n('common.cancel')}
</Button>
</div>
</BlockingLoader>

Aller plus loin