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.tspackages/common/src/lib/widgets/export/report/report.config.tspackages/common/src/lib/widgets/export/report/report.models.tspackages/common/src/lib/widgets/export/report/Report.svelte
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
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
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
<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>