fix(C103): reaplica timeVisible/allDay do C104 perdidos no ciclo de rebase
This commit is contained in:
parent
437b5f0c2f
commit
5abc0e0881
2 changed files with 96 additions and 38 deletions
|
|
@ -18,6 +18,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover
|
|||
import {
|
||||
floorToMinuteStep,
|
||||
formatBahiaCivilDate,
|
||||
formatBahiaCivilDateLabel,
|
||||
formatBahiaCivilDateTimeLabel,
|
||||
hourOptions,
|
||||
minuteOptionsForStep,
|
||||
|
|
@ -37,6 +38,9 @@ import { cn } from '@/lib/utils'
|
|||
* C103 — on narrow viewports the picker opens as a nested bottom sheet
|
||||
* (calendar + hour/minute fully on screen, "Pronto" applies and closes),
|
||||
* keeping the desktop popover untouched.
|
||||
*
|
||||
* C104 — `timeVisible={false}` is the all-day mode: the trigger formats only
|
||||
* the date and the picker renders just the calendar (no time selects).
|
||||
*/
|
||||
export const ActivityDateTimeField = ({
|
||||
id,
|
||||
|
|
@ -47,6 +51,7 @@ export const ActivityDateTimeField = ({
|
|||
isNarrow = false,
|
||||
label,
|
||||
required = false,
|
||||
timeVisible = true,
|
||||
}: {
|
||||
id: string
|
||||
value: string
|
||||
|
|
@ -60,6 +65,8 @@ export const ActivityDateTimeField = ({
|
|||
label?: string
|
||||
/** Marks the required fields with a visible asterisk on the narrow trigger. */
|
||||
required?: boolean
|
||||
/** When false, hide the time selects and label the trigger with the date only. */
|
||||
timeVisible?: boolean
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const civil = floorToMinuteStep(value)
|
||||
|
|
@ -96,7 +103,9 @@ export const ActivityDateTimeField = ({
|
|||
'border-destructive ring-3 ring-destructive/20 focus-visible:border-destructive focus-visible:ring-destructive/20',
|
||||
)}
|
||||
>
|
||||
<span className="truncate">{formatBahiaCivilDateTimeLabel(civil)}</span>
|
||||
<span className="truncate">
|
||||
{timeVisible ? formatBahiaCivilDateTimeLabel(civil) : formatBahiaCivilDateLabel(civil)}
|
||||
</span>
|
||||
{isNarrow && required ? (
|
||||
<span aria-hidden="true" className="font-semibold text-destructive">
|
||||
*
|
||||
|
|
@ -122,38 +131,40 @@ export const ActivityDateTimeField = ({
|
|||
className="w-full p-2"
|
||||
/>
|
||||
) : null}
|
||||
<div className="flex flex-col gap-3 border-t p-3">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-xs font-medium text-muted-foreground select-none">Hora</span>
|
||||
<NativeSelect
|
||||
aria-label="Hora"
|
||||
value={hour}
|
||||
onChange={(event) => setHour(event.target.value)}
|
||||
>
|
||||
{hourOptions.map((option) => (
|
||||
<NativeSelectOption key={option} value={option}>
|
||||
{option}h
|
||||
</NativeSelectOption>
|
||||
))}
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-xs font-medium text-muted-foreground select-none">Minuto</span>
|
||||
<NativeSelect
|
||||
aria-label="Minuto"
|
||||
value={minute}
|
||||
onChange={(event) => setMinute(event.target.value)}
|
||||
>
|
||||
{minuteOptionsForStep().map((option) => (
|
||||
<NativeSelectOption key={option} value={option}>
|
||||
{option}
|
||||
</NativeSelectOption>
|
||||
))}
|
||||
</NativeSelect>
|
||||
{timeVisible ? (
|
||||
<div className="flex flex-col gap-3 border-t p-3">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-xs font-medium text-muted-foreground select-none">Hora</span>
|
||||
<NativeSelect
|
||||
aria-label="Hora"
|
||||
value={hour}
|
||||
onChange={(event) => setHour(event.target.value)}
|
||||
>
|
||||
{hourOptions.map((option) => (
|
||||
<NativeSelectOption key={option} value={option}>
|
||||
{option}h
|
||||
</NativeSelectOption>
|
||||
))}
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-xs font-medium text-muted-foreground select-none">Minuto</span>
|
||||
<NativeSelect
|
||||
aria-label="Minuto"
|
||||
value={minute}
|
||||
onChange={(event) => setMinute(event.target.value)}
|
||||
>
|
||||
{minuteOptionsForStep().map((option) => (
|
||||
<NativeSelectOption key={option} value={option}>
|
||||
{option}
|
||||
</NativeSelectOption>
|
||||
))}
|
||||
</NativeSelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { ResponsibleMultiSelect } from '@/components/campaign/shared/Responsible
|
|||
import { StrictCombobox } from '@/components/campaign/shared/StrictCombobox'
|
||||
import { Alert, AlertDescription } from '@/components/ui/Alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Checkbox } from '@/components/ui/Checkbox'
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
|
|
@ -24,6 +25,7 @@ import { Field, FieldError, FieldLabel } from '@/components/ui/field'
|
|||
import { Input } from '@/components/ui/input'
|
||||
import { Popover, PopoverAnchor, PopoverContent } from '@/components/ui/Popover'
|
||||
import { Spinner } from '@/components/ui/Spinner'
|
||||
import { allDayEndInstant, allDayRangeValid, allDayStartInstant } from '@/lib/activityAllDay'
|
||||
import {
|
||||
formatBahiaDateTimeLabel,
|
||||
formatIsoAsBahiaDateTimeInput,
|
||||
|
|
@ -113,6 +115,8 @@ type InlineDateTimeFieldProps = {
|
|||
invalid: boolean
|
||||
error?: string
|
||||
required?: boolean
|
||||
/** C104 — false hides the time selects (all-day mode). */
|
||||
timeVisible?: boolean
|
||||
}
|
||||
|
||||
const InlineDateTimeField = ({
|
||||
|
|
@ -124,6 +128,7 @@ const InlineDateTimeField = ({
|
|||
invalid,
|
||||
error,
|
||||
required = false,
|
||||
timeVisible = true,
|
||||
}: InlineDateTimeFieldProps) => (
|
||||
<Field data-invalid={invalid}>
|
||||
<FieldLabel htmlFor={id} className={sheet ? 'sr-only' : undefined}>
|
||||
|
|
@ -141,6 +146,7 @@ const InlineDateTimeField = ({
|
|||
isNarrow={sheet}
|
||||
label={label}
|
||||
required={required}
|
||||
timeVisible={timeVisible}
|
||||
/>
|
||||
{error ? <FieldError id={`${id}-error`}>{error}</FieldError> : null}
|
||||
</Field>
|
||||
|
|
@ -167,6 +173,7 @@ const ActivityInlineCreateForm = ({
|
|||
const [title, setTitle] = useState('')
|
||||
const [start, setStart] = useState(formatIsoAsBahiaDateTimeInput(startAt))
|
||||
const [end, setEnd] = useState(formatIsoAsBahiaDateTimeInput(endAt))
|
||||
const [allDay, setAllDay] = useState(false)
|
||||
const [locality, setLocality] = useState('')
|
||||
const [tags, setTags] = useState<string[]>([])
|
||||
const preselectedMunicipalityId =
|
||||
|
|
@ -181,12 +188,19 @@ const ActivityInlineCreateForm = ({
|
|||
|
||||
const errorFor = (name: string) => fieldErrors[name]?.[0]
|
||||
|
||||
const allDayStartDate = start.slice(0, 10)
|
||||
const allDayEndDate = end ? end.slice(0, 10) : allDayStartDate
|
||||
|
||||
// C105 — the href mirrors the LIVE state (times edited in the sheet, typed
|
||||
// tags), not the slot draft, so "Mais detalhes" carries everything the user
|
||||
// actually filled.
|
||||
const moreDetailsHref = buildActivityCreateHref(agendaState, {
|
||||
startAt: parseBahiaDateTimeInput(start) ?? undefined,
|
||||
endAt: end ? (parseBahiaDateTimeInput(end) ?? undefined) : undefined,
|
||||
...(allDay
|
||||
? { allDay: true, startAt: allDayStartDate, endAt: allDayEndDate }
|
||||
: {
|
||||
startAt: parseBahiaDateTimeInput(start) ?? undefined,
|
||||
endAt: end ? (parseBahiaDateTimeInput(end) ?? undefined) : undefined,
|
||||
}),
|
||||
municipalityId: municipalityValue ? Number(municipalityValue) : undefined,
|
||||
title: title.trim() || undefined,
|
||||
tags: tags.length > 0 ? tags : undefined,
|
||||
|
|
@ -207,9 +221,17 @@ const ActivityInlineCreateForm = ({
|
|||
nextFieldErrors.title = ['Informe um título com ao menos 2 caracteres.']
|
||||
}
|
||||
if (!municipalityId) nextFieldErrors.municipality = ['Informe o município.']
|
||||
if (!startIso) nextFieldErrors.startAt = ['Informe a data e horário de início do compromisso.']
|
||||
if (startIso && endIso && new Date(endIso) <= new Date(startIso)) {
|
||||
nextFieldErrors.endAt = ['O horário de término deve ser posterior ao de início.']
|
||||
if (allDay) {
|
||||
if (!startIso) nextFieldErrors.startAt = ['Informe a data de início do compromisso.']
|
||||
if (startIso && endIso && !allDayRangeValid(startIso, endIso)) {
|
||||
nextFieldErrors.endAt = ['A data de término deve ser igual ou posterior à de início.']
|
||||
}
|
||||
} else {
|
||||
if (!startIso)
|
||||
nextFieldErrors.startAt = ['Informe a data e horário de início do compromisso.']
|
||||
if (startIso && endIso && new Date(endIso) <= new Date(startIso)) {
|
||||
nextFieldErrors.endAt = ['O horário de término deve ser posterior ao de início.']
|
||||
}
|
||||
}
|
||||
if (Object.keys(nextFieldErrors).length > 0) {
|
||||
setFieldErrors(nextFieldErrors)
|
||||
|
|
@ -224,8 +246,16 @@ const ActivityInlineCreateForm = ({
|
|||
const result = await createActivityInline({
|
||||
title: trimmedTitle,
|
||||
municipality: municipalityId,
|
||||
startAt: startIso as string,
|
||||
...(endIso ? { endAt: endIso } : {}),
|
||||
...(allDay
|
||||
? {
|
||||
allDay: true,
|
||||
startAt: allDayStartInstant(allDayStartDate),
|
||||
...(end ? { endAt: allDayEndInstant(allDayEndDate) } : {}),
|
||||
}
|
||||
: {
|
||||
startAt: startIso as string,
|
||||
...(endIso ? { endAt: endIso } : {}),
|
||||
}),
|
||||
...(locality.trim() ? { locality: locality.trim() } : {}),
|
||||
...(responsible ? { responsible } : {}),
|
||||
// C105 — the tags mirror state (every chip mutation fires `onChange`),
|
||||
|
|
@ -259,6 +289,7 @@ const ActivityInlineCreateForm = ({
|
|||
invalid={Boolean(errorFor('startAt'))}
|
||||
error={errorFor('startAt')}
|
||||
required
|
||||
timeVisible={!allDay}
|
||||
/>
|
||||
<InlineDateTimeField
|
||||
sheet={sheet}
|
||||
|
|
@ -268,6 +299,7 @@ const ActivityInlineCreateForm = ({
|
|||
onValueChange={setEnd}
|
||||
invalid={Boolean(errorFor('endAt'))}
|
||||
error={errorFor('endAt')}
|
||||
timeVisible={!allDay}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
|
@ -307,6 +339,21 @@ const ActivityInlineCreateForm = ({
|
|||
) : null}
|
||||
</Field>
|
||||
|
||||
{/* C104 — all-day toggle: hides the time part of Início/Término. */}
|
||||
<Field
|
||||
orientation="horizontal"
|
||||
className={
|
||||
sheet ? 'min-h-11 gap-2 rounded-none border-0 px-0' : 'min-h-11 rounded-lg border p-3'
|
||||
}
|
||||
>
|
||||
<Checkbox
|
||||
checked={allDay}
|
||||
onCheckedChange={(next) => setAllDay(Boolean(next))}
|
||||
aria-label="Todo o dia"
|
||||
/>
|
||||
<span>Todo o dia</span>
|
||||
</Field>
|
||||
|
||||
{sheet ? (
|
||||
startEndFields
|
||||
) : (
|
||||
|
|
|
|||
Loading…
Reference in a new issue