Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
729855d
feat(users): add user type classification from Portail DF
loicguillois Dec 2, 2025
3474303
fix(models): add missing 'kind' property to UserDTO fixture
loicguillois Dec 4, 2025
2173d80
fix(server): add missing 'kind' property to all User-related code
loicguillois Dec 4, 2025
65a41f8
fix(frontend,server): add missing 'kind' property to User type mappings
loicguillois Dec 8, 2025
a12387b
fix(api): add authentication and retry logic to Portail DF API calls
loicguillois Dec 9, 2025
a801591
feat(sync-user-kind): add token-based authentication for Portail DF API
loicguillois Dec 17, 2025
c9b14c3
fix(server): add batching for seed queries and config check for userKind
loicguillois Dec 18, 2025
3092fca
refactor(auth): remove duplicated user update code
loicguillois Jan 8, 2026
e79cfaa
Merge pull request #1494 from MTES-MCT/feat/add-prestataires-column-u…
loicguillois Jan 8, 2026
068288d
test(frontend): add MSW handler to replace housing precisions
Falinor Jan 5, 2026
f0b8272
refactor(frontend): rename PrecisionModalNext to PrecisionModal
Falinor Jan 5, 2026
67c72c4
feat(frontend): add null option for radio inputs in PrecisionColumn
Falinor Jan 6, 2026
e459689
test(frontend): update radio options filtering to exclude "Pas d’info…
Falinor Jan 6, 2026
fed478b
Merge pull request #1532 from MTES-MCT/feat/add-empty-option-to-preci…
Falinor Jan 8, 2026
356490a
feat(frontend): extract HousingEditionNoteTab component and integrate…
Falinor Jan 6, 2026
857ea6c
feat(frontend): add hideIcon prop to HistoryCard and NoteCard components
Falinor Jan 6, 2026
3e4444a
fix(frontend): ensure document rename modal closes on cancel action
Falinor Jan 6, 2026
004e974
feat(frontend): prevent users to edit or remove notes from the housin…
Falinor Jan 8, 2026
5daa3a6
fix(frontend): remove unused onOpen function and call onCancel in Doc…
Falinor Jan 8, 2026
1335f0a
Merge pull request #1533 from MTES-MCT/feat/list-housing-notes
Falinor Jan 12, 2026
229ce98
fix(frontend): update maplibre-gl to v5.16.0 to display the map corre…
Falinor Jan 14, 2026
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 frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"jose": "4.15.9",
"lexical": "0.38.2",
"lodash-es": "4.17.21",
"maplibre-gl": "5.13.0",
"maplibre-gl": "5.16.0",
"mime": "4.1.0",
"msw": "2.12.4",
"posthog-js": "1.298.0",
Expand Down
38 changes: 21 additions & 17 deletions frontend/src/components/EventsHistory/HistoryCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { PropsWithChildren } from 'react';

export type HistoryCardProps = PropsWithChildren<{
icon: FrIconClassName | RiIconClassName;
hideIcon?: boolean;
}>;

function HistoryCard(props: HistoryCardProps) {
Expand All @@ -14,25 +15,28 @@ function HistoryCard(props: HistoryCardProps) {
component="article"
direction="row"
spacing="1rem"
useFlexGap
sx={{ alignItems: 'center' }}
>
<Box
sx={{
display: 'flex',
background: fr.colors.decisions.background.alt.grey.hover,
borderRadius: '50%',
padding: '0.25rem',
alignItems: 'center',
justifyContent: 'center',
gap: '0.625rem',
flexShrink: 0,
aspectRatio: '1/1',
width: '2.375rem',
height: '2.375rem'
}}
>
<span className={props.icon} />
</Box>
{!props.hideIcon && (
<Box
sx={{
display: 'flex',
background: fr.colors.decisions.background.alt.grey.hover,
borderRadius: '50%',
padding: '0.25rem',
alignItems: 'center',
justifyContent: 'center',
gap: '0.625rem',
flexShrink: 0,
aspectRatio: '1/1',
width: '2.375rem',
height: '2.375rem'
}}
>
<span className={props.icon} />
</Box>
)}

{props.children}
</Stack>
Expand Down
16 changes: 12 additions & 4 deletions frontend/src/components/EventsHistory/NoteCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import localeFR from 'date-fns/locale/fr';
import { useMemo, useState } from 'react';
import { FormProvider, type SubmitHandler, useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import * as yup from 'yup';

import { useNotification } from '../../hooks/useNotification';
import { useUser } from '../../hooks/useUser';

import * as yup from 'yup';
import type { Establishment } from '../../models/Establishment';
import type { Note } from '../../models/Note';
import { formatAuthor } from '../../models/User';
Expand All @@ -29,6 +29,12 @@ import HistoryCard from './HistoryCard';
export interface NoteCardProps {
note: Note;
establishment: Pick<Establishment, 'name'> | null;
hideIcon?: boolean;
/**
* If true, the note cannot be edited or deleted.
* @default false
*/
readOnly?: boolean;
}

function NoteCard(props: NoteCardProps) {
Expand Down Expand Up @@ -66,7 +72,9 @@ function NoteCard(props: NoteCardProps) {
});

const { isAdmin, isUsual, user } = useUser();
const canUpdate = isAdmin || (isUsual && user?.id === props.note.createdBy);
const canUpdate =
!props.readOnly &&
(isAdmin || (isUsual && user?.id === props.note.createdBy));

const removeModal = useMemo(
() =>
Expand Down Expand Up @@ -123,7 +131,7 @@ function NoteCard(props: NoteCardProps) {
}

return (
<HistoryCard icon="ri-message-line">
<HistoryCard icon="ri-message-line" hideIcon={props.hideIcon}>
<removeModal.Component
className="fr-ml-0"
title="Suppression d’une note"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ export function createDocumentRenameModal() {
});

function onClose(): void {
props.onCancel();
}

function onOpen(): void {
form.reset();
}

Expand All @@ -65,7 +61,6 @@ export function createDocumentRenameModal() {
{...rest}
title="Renommer le document"
onClose={onClose}
onOpen={onOpen}
onSubmit={form.handleSubmit(onSubmit)}
>
{document ? (
Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/HousingDetails/DocumentsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ function DocumentsTab() {

function onCancelRename(): void {
setSelectedDocument(null);
documentRenameModal.close();
}

function rename(filename: string): void {
Expand Down
44 changes: 44 additions & 0 deletions frontend/src/components/HousingEdition/HousingEditionNoteTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Stack from '@mui/material/Stack';
import { skipToken } from '@reduxjs/toolkit/query';

import AppTextInputNext from '~/components/_app/AppTextInput/AppTextInputNext';
import NoteCard from '~/components/EventsHistory/NoteCard';
import type { Housing } from '~/models/Housing';
import { useFindNotesByHousingQuery } from '~/services/note.service';

interface Props {
housingId: Housing['id'] | null;
}

function HousingEditionNoteTab(props: Props) {
const { data: notes = [] } = useFindNotesByHousingQuery(
props.housingId ?? skipToken
);

return (
<Stack rowGap={2}>
<AppTextInputNext
label="Nouvelle note"
name="note"
nativeTextAreaProps={{ rows: 8 }}
textArea
/>

{notes.length > 0 && (
<Stack spacing="1rem">
{notes.map((note) => (
<NoteCard
key={note.id}
note={note}
establishment={null}
hideIcon
readOnly
/>
))}
</Stack>
)}
</Stack>
);
}

export default HousingEditionNoteTab;
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ import { HousingStates } from '../../models/HousingState';
import { useUpdateHousingMutation } from '../../services/housing.service';
import { useCreateNoteByHousingMutation } from '../../services/note.service';
import AppLink from '../_app/AppLink/AppLink';
import AppTextInputNext from '../_app/AppTextInput/AppTextInputNext';
import OccupancySelect from '../HousingListFilters/OccupancySelect';
import AsideNext from '../Aside/AsideNext';
import LabelNext from '../Label/LabelNext';
import HousingEditionMobilizationTab from './HousingEditionMobilizationTab';
import HousingEditionNoteTab from './HousingEditionNoteTab';
import { useHousingEdition } from './useHousingEdition';
import type { Housing, HousingUpdate } from '../../models/Housing';
import type { HousingEditionContext } from './useHousingEdition';
Expand Down Expand Up @@ -173,12 +173,7 @@ function HousingEditionSideMenu(props: HousingEditionSideMenuProps) {
<HousingEditionMobilizationTab housingId={housing?.id ?? null} />
))
.with('note', () => (
<AppTextInputNext
label="Nouvelle note"
name="note"
nativeTextAreaProps={{ rows: 8 }}
textArea
/>
<HousingEditionNoteTab housingId={housing?.id ?? null} />
))
.exhaustive();

Expand Down
85 changes: 71 additions & 14 deletions frontend/src/components/Precision/PrecisionColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,84 @@ import type { CheckboxProps } from '@codegouvfr/react-dsfr/Checkbox';
import RadioButtons from '@codegouvfr/react-dsfr/RadioButtons';
import Typography from '@mui/material/Typography';
import classNames from 'classnames';
import type { ChangeEvent } from 'react';
import type { ElementOf } from 'ts-essentials';

import type { Precision, PrecisionCategory } from '@zerologementvacant/models';
import { NULL_PRECISION_ID } from '../../models/Precision';
import styles from './precision-modal.module.scss';

interface PrecisionColumnProps {
type PrecisionColumnCommonProps = {
category: PrecisionCategory;
icon: FrIconClassName | RiIconClassName;
options: Precision[];
value: Precision[];
title: string;
/**
* @default 'checkbox'
*/
input?: 'checkbox' | 'radio';
onChange(event: ChangeEvent<HTMLInputElement>): void;
}
};

type PrecisionColumnCheckboxProps = PrecisionColumnCommonProps & {
input?: 'checkbox';
value: Precision[];
onChange(value: Precision[]): void;
};

type PrecisionColumnRadioProps = PrecisionColumnCommonProps & {
input: 'radio';
value: Precision | null;
onChange(value: Precision | null): void;
};

type PrecisionColumnProps =
| PrecisionColumnCheckboxProps
| PrecisionColumnRadioProps;

function PrecisionColumn(props: PrecisionColumnProps) {
const Fieldset = props.input === 'radio' ? RadioButtons : Checkbox;
const isRadio = props.input === 'radio';
const Fieldset = isRadio ? RadioButtons : Checkbox;

// Add null option for radio inputs
const nullOption: Precision | null = isRadio
? {
id: NULL_PRECISION_ID,
label: 'Pas d’information',
category: props.category
}
: null;

const allOptions = nullOption
? [nullOption, ...props.options]
: props.options;

function isOptionChecked(option: Precision): boolean {
if (option.id === NULL_PRECISION_ID) {
// Null option is checked when there's no selection
return isRadio && props.value === null;
}

if (isRadio) {
return props.value?.id === option.id;
} else {
return props.value.some((value) => value.id === option.id);
}
}

function handleOptionClick(option: Precision, checked: boolean): void {
if (isRadio) {
// Radio button mode: always set the clicked option
if (option.id === NULL_PRECISION_ID) {
props.onChange(null);
} else {
props.onChange(option);
}
} else {
// Checkbox mode: toggle
if (checked) {
props.onChange([...props.value, option]);
} else {
props.onChange(
props.value.filter((precision) => precision.id !== option.id)
);
}
}
}

return (
<>
Expand All @@ -36,13 +93,13 @@ function PrecisionColumn(props: PrecisionColumnProps) {
{props.title}
</Typography>
<Fieldset
options={props.options.map(
options={allOptions.map(
(option): ElementOf<CheckboxProps['options']> => ({
label: option.label,
nativeInputProps: {
checked: props.value.some((value) => value.id === option.id),
value: option.id,
onChange: props.onChange
checked: isOptionChecked(option),
onChange: () =>
handleOptionClick(option, !isOptionChecked(option))
}
})
)}
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/Precision/PrecisionLists.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
useSaveHousingPrecisionsMutation
} from '../../services/precision.service';
import styles from '../HousingEdition/housing-edition.module.scss';
import createPrecisionModalNext from './PrecisionModalNext';
import createPrecisionModal from './PrecisionModal';
import type { PrecisionTabId } from './PrecisionTabs';
import { useFilteredPrecisions } from './useFilteredPrecisions';

Expand All @@ -29,7 +29,7 @@ interface Props {

function PrecisionLists(props: Props) {
const precisionModal = useMemo(
() => createPrecisionModalNext(new Date().toJSON()),
() => createPrecisionModal(new Date().toJSON()),
[]
);

Expand Down Expand Up @@ -96,7 +96,7 @@ function PrecisionLists(props: Props) {
if (props.housingId) {
saveHousingPrecisions({
housing: props.housingId,
precisions: precisions.map((p) => p.id)
precisions: precisions.map((precision) => precision.id)
}).then(() => {
precisionModal.close();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type PrecisionModalProps = Omit<
onSubmit(value: Precision[]): void;
};

function createPrecisionModalNext(id: string) {
function createPrecisionModal(id: string) {
const precisionModalOptions = {
id: `precision-modal-${id}`,
isOpenedByDefault: false
Expand Down Expand Up @@ -59,4 +59,4 @@ function createPrecisionModalNext(id: string) {
};
}

export default createPrecisionModalNext;
export default createPrecisionModal;
Loading
Loading