Skip to content

Commit

Permalink
feat(procu): histo, better error, better tag, better ux (#3042)
Browse files Browse the repository at this point in the history
  • Loading branch information
OverGlass committed Jun 24, 2024
1 parent 41e93c1 commit 03afa87
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 40 deletions.
11 changes: 11 additions & 0 deletions src/api/Procuration/procuration.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface ProcurationModel extends ReadableLightUserModel {
status: ProcurationStatusEnum
matched_at: string | null
matcher: ReadableLightUserModelWithSingleFirstName | null
actions: ActionModel[] | null
// Indicate if mandate will sign procuration in French soil
from_france: boolean
}
Expand Down Expand Up @@ -78,6 +79,15 @@ export interface RoundModel {
date: string
}

export interface ActionModel {
uuid: string
status: 'match' | 'unmatch' | 'status_update'
date: string
author: ReadableLightUserModelWithSingleFirstName | null
author_scope: string | null
context: Record<string, string>
}

export interface SlotModel {
uuid: string
created_at: string
Expand All @@ -87,6 +97,7 @@ export interface SlotModel {
proxy: null | ProcurationProxyDetailModel
matched_at: string | null
matcher: ReadableLightUserModelWithSingleFirstName | null
actions: ActionModel[] | null
}

export interface PostAddressModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ import {
MandatePersonCardStateExclude,
} from '~/components/Procurations/Components/MandantTab/Components/MandatePersonCard/Components/MandatePersonCardStateActions'
import MandatePersonCardButtonGroup from '~/components/Procurations/Components/MandantTab/Components/MandatePersonCard/Components/MandatePersonCardButtonGroup'
import { PROCURATION_STATUS_LABELS, ProcurationStatusEnum, SlotModel } from '~/api/Procuration/procuration.model'
import { getFormattedDate } from '~/utils/date'
import {
ActionModel,
PROCURATION_STATUS_LABELS,
ProcurationStatusEnum,
SlotModel,
} from '~/api/Procuration/procuration.model'
import { getHumanFormattedDate, getHumanFormattedTime } from '~/utils/date'

export interface MandatePersonCardProps {
firstName: string
Expand Down Expand Up @@ -50,6 +55,7 @@ export interface MandatePersonCardProps {
onPersonView?: (id: string, roundId: string) => void
// Will deposit mandate in France
inFrenchSoil?: boolean
history: ActionModel[] | null
}

export enum MandatePersonCardType {
Expand Down Expand Up @@ -115,36 +121,34 @@ export default function MandatePersonCard(props: MandatePersonCardProps) {
{linkedPeople?.map(x => (
<Fragment key={x.uuid + props.id}>
<Grid item xs={12}>
<Typography variant="h6" sx={{ mt: MuiSpacing.normal }}>
<Typography variant="h6" sx={{ mt: MuiSpacing.normal, mb: MuiSpacing.normal }}>
{x.round.name}
</Typography>
</Grid>

<Grid container justifyItems="center">
<>
{([MandatePersonCardType.FIND].includes(props.type) || props.roundId === x.round.uuid) &&
x.proxy.length < 1 && (
<Grid item xs={8} py={2} pr={MuiSpacing.normal}>
<MandatePersonCardButtonGroup
fullWidth
{...props}
disabled={x.manual}
onSelect={() => props.onSelect?.(x.round.uuid)}
extraText={x.round.name}
/>
</Grid>
)}
{[MandatePersonCardType.FIND].includes(props.type) && x.proxy.length < 1 && (
<Grid item xs={4} py={2}>
<MandatePersonCardStateManual {...props} currentSlot={x} />
{([MandatePersonCardType.FIND].includes(props.type) || props.roundId === x.round.uuid) &&
x.proxy.length < 1 && (
<Grid item xs={8} pr={MuiSpacing.normal} sx={{ my: 2 }}>
<MandatePersonCardButtonGroup
fullWidth
{...props}
disabled={x.manual}
onSelect={() => props.onSelect?.(x.round.uuid)}
extraText={x.round.name}
/>
</Grid>
)}
</>
{[MandatePersonCardType.FIND].includes(props.type) && x.proxy.length < 1 && (
<Grid item xs={4} sx={{ my: 2 }}>
<MandatePersonCardStateManual {...props} currentSlot={x} />
</Grid>
)}
</Grid>
{(([MandatePersonCardType.MATCHED_MANDANT, MandatePersonCardType.MATCHED_PROXY].includes(props.type) &&
x.manual) ||
([MandatePersonCardType.MATCH_PROXY].includes(props.type) && !props.roundId && !x.proxy?.length)) && (
<Grid item xs={12} pb={4}>
<Grid item xs={12} sx={{ mb: 2 }}>
<MandatePersonCardStateManual {...props} currentSlot={x} />
</Grid>
)}
Expand All @@ -158,7 +162,7 @@ export default function MandatePersonCard(props: MandatePersonCardProps) {
)}

{x.proxy && x.proxy.length > 0 && (
<Grid item sx={{ mb: MuiSpacing.large }}>
<Grid item xs={12}>
<GroupContainer>
<legend>
<Typography color={'success.main'} fontSize={12}>
Expand Down Expand Up @@ -187,17 +191,11 @@ export default function MandatePersonCard(props: MandatePersonCardProps) {
) : null
)}
</GroupContainer>
{x.matched_at && (
<Grid item mt={MuiSpacing.small}>
<Typography fontSize={14} color={'text.secondary'}>
Lié le {getFormattedDate(x.matched_at)} par {x.matcher?.first_name} {x.matcher?.last_name}
</Typography>
</Grid>
)}
</Grid>
)}
<Grid item xs={12}>
<Divider sx={{ mt: MuiSpacing.normal }} />
<HistoDetail data={x.actions} />
<Divider sx={{ mt: MuiSpacing.normal, backgroundColor: 'black' }} />
</Grid>
</Fragment>
))}
Expand All @@ -218,46 +216,108 @@ export default function MandatePersonCard(props: MandatePersonCardProps) {

{props.onNarrow && <Divider sx={withBottomSpacing} />}

{props.history && <HistoDetail data={props.history} />}

{props.history && props.history.length > 0 && <Divider sx={withBottomSpacing} />}

{props.onNarrow && <NarrowButton onNarrow={() => props.onNarrow?.(props.id)} />}
</>
)}
</Paper>
)
}

const NarrowButton = ({ onNarrow }: { onNarrow?: () => void }) => (
const HistoDetail = ({ data }: { data: SlotModel['actions'] }) => {
const mapStatusReadable = (
status: NonNullable<SlotModel['actions']>[0]['status'],
ctx: NonNullable<SlotModel['actions']>[0]['context']
) => {
switch (status) {
case 'match':
return 'Lié'
case 'unmatch':
return 'Délié'
case 'status_update':
if (ctx.new_status === 'pending') {
return 'Passé en attente'
}
if (ctx.new_status === 'excluded') {
return 'Exclu'
}

if (ctx.new_status === 'manual') {
return 'Traité manuellement'
}
return 'Statut mis à jour'
default:
return status
}
}

return data && data.length > 0 ? (
<Grid item xs={12} pb={2}>
<Typography variant="body2" color="textSecondary">
Historique
</Typography>
{data.map(({ status, date, author, author_scope, context }) => (
<Grid key={date} item xs={12} sx={{ paddingX: 2 }}>
<Divider sx={{ mt: MuiSpacing.normal }} />
<Typography variant="body2" color="textSecondary">
{mapStatusReadable(status, context)} le {getHumanFormattedDate(date)} à {getHumanFormattedTime(date)}
{author ? ` par ${author?.first_name} ${author?.last_name}` : ''}
{author_scope ? ` (${author_scope})` : ''}
</Typography>
{/* <Divider sx={withBottomSpacing} /> */}
</Grid>
))}
</Grid>
) : null
}

const NarrowButton = ({ onNarrow, text }: { onNarrow?: () => void; text?: string }) => (
<Grid item textAlign={'center'}>
<Button
variant={'text'}
startIcon={<Iconify icon="eva:arrow-ios-upward-fill" />}
onClick={onNarrow}
data-testid="lessButton"
>
Afficher moins
{text ?? 'Afficher moins'}
</Button>
</Grid>
)

const ExpandButton = ({ onExpand }: { onExpand?: () => void }) => (
const ExpandButton = ({ onExpand, text }: { onExpand?: () => void; text?: string }) => (
<Grid item textAlign={'center'}>
<Button
variant={'text'}
startIcon={<Iconify icon="eva:arrow-ios-downward-fill" />}
onClick={onExpand}
data-testid="moreButton"
>
Afficher plus
{text ?? 'Afficher plus'}
</Button>
</Grid>
)

const mapColor = (status: string, defaultColor: string) => {
switch (status) {
case 'excluded':
return 'red'
case 'completed':
return 'green'
default:
return defaultColor
}
}

const MandateTag = ({ status }: { status: string }) => (
<UIChip
label={PROCURATION_STATUS_LABELS[status] ?? status}
labelStyle={{ fontSize: '14px', fontWeight: fontWeight.medium }}
color={'white'}
variant={'contained'}
bgcolor={'#00B8D9'}
bgcolor={mapColor(status, '#00B8D9')}
/>
)

Expand All @@ -267,7 +327,7 @@ const ProxyTag = ({ status }: { status: string }) => (
labelStyle={{ fontSize: '14px', fontWeight: fontWeight.medium }}
color={'white'}
variant={'contained'}
bgcolor={'#FF8438'}
bgcolor={mapColor(status, '#FF8438')}
/>
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ const MandateItemComponent = ({

return (
<MandatePersonCard
history={item.actions}
hideActions={done}
uuid={item.uuid}
status={item.status}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ const ProxyItemComponent = ({

return (
<MandatePersonCard
history={item.actions}
hideActions
uuid={item.uuid}
firstName={item.first_names}
Expand Down
2 changes: 2 additions & 0 deletions src/components/Procurations/Pages/MandateEditPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export default function MandateEditPage() {
<Grid item xs={12} sm={12} md={6}>
{data ? (
<MandatePersonCard
history={data.actions}

Check failure on line 123 in src/components/Procurations/Pages/MandateEditPage.tsx

View workflow job for this annotation

GitHub Actions / Build

Property 'actions' does not exist on type 'ProcurationDetailsModel'.
firstName={data.first_names}
lastName={data.last_name}
status={data.status}
Expand All @@ -141,6 +142,7 @@ export default function MandateEditPage() {
<Grid item xs={12} sm={12} md={6}>
{proxy ? (
<MandatePersonCard
history={data.actions}

Check failure on line 145 in src/components/Procurations/Pages/MandateEditPage.tsx

View workflow job for this annotation

GitHub Actions / Build

'data' is possibly 'undefined'.

Check failure on line 145 in src/components/Procurations/Pages/MandateEditPage.tsx

View workflow job for this annotation

GitHub Actions / Build

Property 'actions' does not exist on type 'ProcurationDetailsModel'.
firstName={proxy.first_names}
lastName={proxy.last_name}
status={proxy.status}
Expand Down
17 changes: 13 additions & 4 deletions src/components/shared/error/hooks.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getFormattedErrorMessages, handleGenericHttpErrors } from './helpers'
import * as Sentry from '@sentry/react'

const hasErrorDetail = x => typeof x === 'object' && x !== null && 'detail' in x
const hasErrorMessage = x => typeof x === 'object' && x !== null && 'message' in x

export const useErrorHandler = () => {
const [errorMessages, setErrorMessages] = useState([])
Expand All @@ -22,13 +23,21 @@ export const useErrorHandler = () => {

const handleError = useCallback(
error => {
const { response = { data }, stack, message } = error
const { response, stack, message } = error
const { status, data } = response
const errorMessage = hasErrorDetail(data) ? data.detail : message
handleGenericHttpErrors(snackBarWithOptions, status, stack, errorMessage)
let mess = message
if (hasErrorMessage(data)) {
mess = data.message
}

if (hasErrorDetail(data)) {
mess = data.detail
}

handleGenericHttpErrors(snackBarWithOptions, status, stack, mess)
const formattedErrorMessages = getFormattedErrorMessages(data)
setErrorMessages(formattedErrorMessages)
setErrorRawMessage(errorMessage)
setErrorRawMessage(mess)
Sentry.addBreadcrumb({
category: 'request',
message: Object.keys(data).length ? JSON.stringify(data) : message,
Expand Down
2 changes: 2 additions & 0 deletions src/theme/palette.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ export const tagsColor = {
variant2Background: '#FFF0DE',
variant3Text: '#762BD8',
variant3Background: '#EEE2FF',
variant4Text: '#D80000',
variant4Background: '#FFD6D6',
}

export const action = {
Expand Down

0 comments on commit 03afa87

Please sign in to comment.