Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: signalement V1 #919

Merged
merged 10 commits into from
Jan 2, 2025
Merged
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
2 changes: 1 addition & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ NEXT_PUBLIC_MATOMO_SITE_ID=
NEXT_PUBLIC_MATOMO_TRACKER_URL=https://stats.beta.gouv.fr/
NEXT_PUBLIC_BAL_ADMIN_URL=
NEXT_PUBLIC_API_SIGNALEMENT=
PORT=3000
PORT=3000
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Elles peuvent être définies classiquement ou en créant un fichier `.env` sur
| `NEXT_PUBLIC_PEERTUBE` | URL du peertube |
| `NEXT_PUBLIC_MATOMO_TRACKER_URL` | URL du matomo |
| `NEXT_PUBLIC_MATOMO_SITE_ID` | Id du site sur matomo |
| `NEXT_PUBLIC_API_SIGNALEMENT` | URL de l'API signalement |
| `NEXT_PUBLIC_BAL_ADMIN_URL` | URL de base de bal admin |
| `PORT` | Port de l'application |

Expand Down
29 changes: 6 additions & 23 deletions components/bal/address-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,14 @@
import { Pane, Text } from "evergreen-ui";

import { computeCompletNumero } from "@/lib/utils/numero";
import { CommuneType } from "@/types/commune";

const getAddressPreview = (numero, suffixe, toponyme, voie, commune) => {
const completNumero = computeCompletNumero(numero, suffixe) || "";
if (toponyme) {
return `${completNumero} ${voie}, ${toponyme} - ${commune.nom} (${commune.code})`;
}

if (voie) {
return `${completNumero} ${voie} - ${commune.nom} (${commune.code})`;
}

if (!voie && !toponyme) {
return `${completNumero} - ${commune.nom} (${commune.code})`;
}

return `${completNumero} ${voie} - ${commune.nom} (${commune.code})`;
};
import { getAddressPreview } from "@/lib/utils/address";

interface AddressPreviewProps {
numero: string | number;
suffixe?: string;
selectedNomToponyme: string;
voie: string;
commune: CommuneType;
selectedNomToponyme?: string;
voie?: string;
commune?: CommuneType;
}

function AddressPreview({
Expand All @@ -38,9 +21,9 @@ function AddressPreview({
const address = getAddressPreview(
numero,
suffixe,
commune,
selectedNomToponyme,
voie,
commune
voie
);

return (
Expand Down
13 changes: 10 additions & 3 deletions components/bal/commune-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,20 @@ interface CommuneTabProps {
function CommuneTab({ commune, openRecoveryDialog }: CommuneTabProps) {
const { baseLocale } = useContext(BalDataContext);
const { token } = useContext(TokenContext);
const { signalements } = useContext(SignalementContext);
const { pendingSignalementsCount, archivedSignalementsCount } =
useContext(SignalementContext);

return (
<Pane overflowY="auto">
{!token && <ReadOnlyInfos openRecoveryDialog={openRecoveryDialog} />}
{signalements.length > 0 && (
<SignalementsInfos balId={baseLocale.id} signalements={signalements} />
{(pendingSignalementsCount > 0 || archivedSignalementsCount > 0) && (
<SignalementsInfos
balId={baseLocale.id}
signalementCounts={{
pending: pendingSignalementsCount,
archived: archivedSignalementsCount,
}}
/>
)}
{token && baseLocale.status !== BaseLocale.status.DEMO && (
<HabilitationInfos commune={commune} />
Expand Down
5 changes: 2 additions & 3 deletions components/bal/habilitation-infos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,8 @@ function HabilitationInfos({ commune }: HabilitationInfosProps) {
}
>
<Text marginTop={5} is="p">
Cette Base Adresse Locale détient une habilitation permettant
d&apos;alimenter la Base Adresse Nationale pour la commune de{" "}
{commune.nom}. Cette habilitation est valide jusqu&apos;au{" "}
Toutes les modifications remonteront automatiquement dans la Base
Adresse Nationale jusqu&apos;au{" "}
<b>{format(new Date(habilitation.expiresAt), "dd/MM/yyyy")}</b>.
</Text>
</Alert>
Expand Down
6 changes: 3 additions & 3 deletions components/bal/numero-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
refreshBALSync,
reloadVoies,
} = useContext(BalDataContext);
const { selectedParcelles } = useContext(ParcellesContext);
const { highlightedParcelles } = useContext(ParcellesContext);
const { markers, suggestedNumero, setCompleteNumero } =
useContext(MarkersContext);
const { reloadTiles } = useContext(MapContext);
Expand Down Expand Up @@ -116,7 +116,7 @@
numero: Number(numero),
suffixe: suffixe?.length > 0 ? suffixe.toLowerCase().trim() : null,
comment: comment.length > 0 ? comment : null,
parcelles: selectedParcelles,
parcelles: highlightedParcelles,
certifie: certifie ?? (initialValue?.certifie || false),
};

Expand All @@ -143,7 +143,7 @@
certifie,
toponymeId,
comment,
selectedParcelles,
highlightedParcelles,
]);

const onFormSubmit = useCallback(
Expand Down Expand Up @@ -262,14 +262,14 @@
if (onVoieChanged) {
onVoieChanged();
}
}, []);

Check warning on line 265 in components/bal/numero-editor.tsx

View workflow job for this annotation

GitHub Actions / build (22.x)

React Hook useCallback has a missing dependency: 'onVoieChanged'. Either include it or remove the dependency array. If 'onVoieChanged' changes too often, find the parent component that defines it and wrap that definition in useCallback

const handleNomVoieChange = useCallback((nomVoie) => {
onNomVoieChange(nomVoie);
if (onVoieChanged) {
onVoieChanged();
}
}, []);

Check warning on line 272 in components/bal/numero-editor.tsx

View workflow job for this annotation

GitHub Actions / build (22.x)

React Hook useCallback has a missing dependency: 'onVoieChanged'. Either include it or remove the dependency array. If 'onVoieChanged' changes too often, find the parent component that defines it and wrap that definition in useCallback

useEffect(() => {
let nom = null;
Expand Down
12 changes: 6 additions & 6 deletions components/bal/numero-editor/select-parcelles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
const { isCadastreDisplayed, setIsCadastreDisplayed } =
useContext(MapContext);
const {
selectedParcelles,
setSelectedParcelles,
highlightedParcelles,
setHighlightedParcelles,
setIsParcelleSelectionEnabled,
hoveredParcelle,
handleHoveredParcelle,
Expand All @@ -36,23 +36,23 @@
const addressType = isToponyme ? "toponyme" : "numéro";

useEffect(() => {
setSelectedParcelles(initialParcelles);
setHighlightedParcelles(initialParcelles);
setIsParcelleSelectionEnabled(true);

return () => {
setIsParcelleSelectionEnabled(false);
};
}, []);
}, [setHighlightedParcelles, setIsParcelleSelectionEnabled]);

Check warning on line 45 in components/bal/numero-editor/select-parcelles.tsx

View workflow job for this annotation

GitHub Actions / build (22.x)

React Hook useEffect has a missing dependency: 'initialParcelles'. Either include it or remove the dependency array

return (
<Pane display="flex" flexDirection="column">
<InputLabel
title="Parcelles cadastre"
help={`Depuis la carte, cliquez sur les parcelles que vous souhaitez ajouter au ${addressType}. En précisant les parcelles associées à cette adresse, vous accélérez sa réutilisation par de nombreux services, DDFiP, opérateurs de courrier, de fibre et de GPS.`}
/>
{selectedParcelles.length > 0 ? (
{highlightedParcelles.length > 0 ? (
<Pane display="grid" gridTemplateColumns="1fr 1fr 1fr">
{selectedParcelles.map((parcelle) => {
{highlightedParcelles.map((parcelle) => {
const isHovered = parcelle === hoveredParcelle?.id;

return (
Expand Down
61 changes: 47 additions & 14 deletions components/bal/signalement-infos.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,58 @@
import React from "react";
import NextLink from "next/link";
import { Pane, Text, Heading, Link } from "evergreen-ui";
import { Signalement } from "@/lib/openapi-signalement";
import { Pane, Heading, Button, Alert } from "evergreen-ui";
import { useRouter } from "next/navigation";

interface SignalementInfosProps {
balId: string;
signalements: Signalement[];
signalementCounts: {
pending: number;
archived: number;
};
}

function SignalementInfos({ balId, signalements }: SignalementInfosProps) {
function SignalementInfos({ balId, signalementCounts }: SignalementInfosProps) {
const router = useRouter();
const onClick = () => {
router.push(`/bal/${balId}/signalements`);
};

const signalementBtn = (
<Button
type="button"
onClick={onClick}
width="fit-content"
alignSelf="center"
appearance="primary"
>
Consulter les signalements
</Button>
);

return (
<Pane backgroundColor="white" padding={8} borderRadius={10} margin={8}>
<Pane
display="flex"
flexDirection="column"
backgroundColor="white"
padding={8}
borderRadius={10}
margin={8}
>
<Heading marginBottom={15}>Signalements</Heading>
<Text marginTop={5} is="p">
Vous avez <b>{signalements.length}</b>{" "}
{signalements.length > 1 ? "signalements" : "signalement"} en attente de
traitement.
</Text>
<Link is={NextLink} href={`/bal/${balId}/signalements`}>
Consulter les signalements
</Link>
{signalementCounts.pending > 0 ? (
<Alert
intent="info"
title={
<Pane fontWeight="normal">
Vous avez reçu <b>{signalementCounts.pending}</b>{" "}
{signalementCounts.pending > 1 ? "propositions" : "proposition"}.
</Pane>
}
>
<Pane marginTop="1rem">{signalementBtn}</Pane>
</Alert>
) : (
<Pane>{signalementBtn}</Pane>
)}
</Pane>
);
}
Expand Down
6 changes: 3 additions & 3 deletions components/bal/toponyme-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function ToponymeEditor({
reloadNumeros,
} = useContext(BalDataContext);
const { markers } = useContext(MarkersContext);
const { selectedParcelles } = useContext(ParcellesContext);
const { highlightedParcelles } = useContext(ParcellesContext);
const [ref, setIsFocus] = useFocus(true);

const updateNumerosToponyme = useCallback(
Expand Down Expand Up @@ -89,7 +89,7 @@ function ToponymeEditor({
nom,
nomAlt: Object.keys(nomAlt).length > 0 ? nomAlt : null,
positions: [],
parcelles: selectedParcelles,
parcelles: highlightedParcelles,
};

if (markers) {
Expand Down Expand Up @@ -163,7 +163,7 @@ function ToponymeEditor({
nom,
nomAlt,
markers,
selectedParcelles,
highlightedParcelles,
setToponyme,
closeForm,
refreshBALSync,
Expand Down
16 changes: 16 additions & 0 deletions components/base-locale-card/base-locale-card-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ interface BaseLocaleCardContentProps {
onRemove?: (e: any) => void;
onHide?: (e: any) => void;
isShownHabilitationStatus?: boolean;
pendingSignalementsCount?: number;
}

function BaseLocaleCardContent({
Expand All @@ -50,6 +51,7 @@ function BaseLocaleCardContent({
onSelect,
onRemove,
onHide,
pendingSignalementsCount,
}: BaseLocaleCardContentProps) {
const { status, createdAt, emails } = baseLocale;
const { isMobile } = useContext(LayoutContext);
Expand Down Expand Up @@ -120,6 +122,20 @@ function BaseLocaleCardContent({
</Pane>
)}

{Boolean(pendingSignalementsCount) && (
<Pane
flex={1}
textAlign="center"
margin="auto"
{...(isMobile && { marginBottom: 10 })}
>
<Text display="block">Signalements</Text>
<Text fontWeight="bold" whiteSpace="nowrap">
{pendingSignalementsCount}
</Text>
</Pane>
)}

{commune && (
<Pane
flex={1}
Expand Down
21 changes: 21 additions & 0 deletions components/base-locale-card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ import { ApiGeoService } from "@/lib/geo-api";
import StatusBadge from "@/components/status-badge";
import BaseLocaleCardContent from "@/components/base-locale-card/base-locale-card-content";
import {
BaseLocale,
ExtendedBaseLocaleDTO,
HabilitationDTO,
HabilitationService,
OpenAPI,
} from "@/lib/openapi-api-bal";
import { CommuneApiGeoType } from "@/lib/geo-api/type";
import { Signalement, SignalementsService } from "@/lib/openapi-signalement";
import { canFetchSignalements } from "@/lib/utils/signalement";

const ADRESSE_URL =
process.env.NEXT_PUBLIC_ADRESSE_URL || "https://adresse.data.gouv.fr";
Expand Down Expand Up @@ -52,6 +55,8 @@ function BaseLocaleCard({
const [habilitation, setHabilitation] = useState<HabilitationDTO | null>(
null
);
const [pendingSignalementsCount, setPendingSignalementsCount] =
useState<number>();
const [isHabilitationValid, setIsHabilitationValid] =
useState<boolean>(false);
const [isOpen, setIsOpen] = useState(isAdmin ? isDefaultOpen : false);
Expand Down Expand Up @@ -100,11 +105,26 @@ function BaseLocaleCard({
Object.assign(OpenAPI, { TOKEN: null });
};

const fetchPendingSignalementsCount = async () => {
const paginatedSignalements = await SignalementsService.getSignalements(
1,
undefined,
[Signalement.status.PENDING],
undefined,
undefined,
[baseLocale.commune]
);
setPendingSignalementsCount(paginatedSignalements.total);
};

void fetchCommune();

if (!baseLocale.token) {
void fetchHabilitationIsValid();
} else {
if (canFetchSignalements(baseLocale, baseLocale.token)) {
void fetchPendingSignalementsCount();
}
void fetchHabilitation();
}
}, [baseLocale]);
Expand Down Expand Up @@ -181,6 +201,7 @@ function BaseLocaleCard({
onSelect={onSelect}
onRemove={onRemove}
onHide={onHide}
pendingSignalementsCount={pendingSignalementsCount}
/>
)}
</Card>
Expand Down
8 changes: 5 additions & 3 deletions components/bases-locales-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ interface BasesLocalesListProps {
basesLocales: ExtendedBaseLocaleDTO[];
}

const fuseOptions = {
keys: ["nom", "commune"],
};

function BasesLocalesList({ basesLocales }: BasesLocalesListProps) {
const { removeBAL, getHiddenBal, addHiddenBal } =
useContext(LocalStorageContext);
Expand All @@ -33,9 +37,7 @@ function BasesLocalesList({ basesLocales }: BasesLocalesListProps) {
Router.push(`/bal/${bal.id}`);
};

const [filtered, onFilter] = useFuse(basesLocales, 200, {
keys: ["nom", "commune"],
});
const [filtered, onFilter] = useFuse(basesLocales, 200, fuseOptions);

const onRemove = useCallback(async () => {
await removeBAL(toRemove);
Expand Down
Loading
Loading