Add step date picker and show full renewal dates.
Replace native date inputs with year→month→day glass picker and display exact next renewal dates in the businesses list. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
58408fed07
commit
b50e4fd812
@@ -0,0 +1,223 @@
|
|||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger {
|
||||||
|
width: 100%;
|
||||||
|
min-height: var(--field-height);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
padding: var(--field-padding-y) var(--field-padding-x);
|
||||||
|
font-size: var(--field-font-size);
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
color: var(--text-primary);
|
||||||
|
text-align: left;
|
||||||
|
background: rgba(255, 255, 255, 0.65);
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.35);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger:hover:not(:disabled) {
|
||||||
|
border-color: rgba(var(--primary-rgb) / 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.triggerOpen,
|
||||||
|
.trigger:focus-visible {
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 3px rgba(var(--primary-rgb) / 0.12);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger:disabled {
|
||||||
|
opacity: 0.65;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.triggerPlaceholder .triggerText {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.triggerText {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
position: static;
|
||||||
|
margin-top: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
-webkit-backdrop-filter: blur(16px);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
box-shadow: var(--glass-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panelHeader {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backBtn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
transition: background 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backBtn:hover {
|
||||||
|
background: rgba(var(--primary-rgb) / 0.1);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.backSpacer {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepChip {
|
||||||
|
min-height: 28px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
background: rgba(148, 163, 184, 0.12);
|
||||||
|
transition: background 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepChip:hover:not(.stepStatic) {
|
||||||
|
background: rgba(var(--primary-rgb) / 0.12);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepActive {
|
||||||
|
background: rgba(var(--primary-rgb) / 0.16);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepStatic {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yearList {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
max-height: 220px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monthGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionBtn {
|
||||||
|
min-height: 36px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
background: rgba(255, 255, 255, 0.55);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionBtn:hover {
|
||||||
|
background: rgba(var(--primary-rgb) / 0.1);
|
||||||
|
border-color: rgba(var(--primary-rgb) / 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionSelected {
|
||||||
|
background: rgba(var(--primary-rgb) / 0.14);
|
||||||
|
border-color: rgba(var(--primary-rgb) / 0.35);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionToday:not(.optionSelected) {
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(var(--primary-rgb) / 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dayPanel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekdayRow,
|
||||||
|
.dayGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dayEmpty {
|
||||||
|
min-height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dayBtn {
|
||||||
|
min-height: 34px;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
transition: background 0.15s, color 0.15s, box-shadow 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dayBtn:hover {
|
||||||
|
background: rgba(var(--primary-rgb) / 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.daySelected {
|
||||||
|
background: var(--primary) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
box-shadow: 0 2px 8px rgba(var(--primary-rgb) / 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dayToday:not(.daySelected) {
|
||||||
|
background: rgba(var(--primary-rgb) / 0.14);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dayPrefill:not(.daySelected):not(.dayToday) {
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(var(--primary-rgb) / 0.35);
|
||||||
|
}
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
import { useEffect, useId, useMemo, useRef, useState } from 'react'
|
||||||
|
import { CalendarDays, ChevronLeft } from 'lucide-react'
|
||||||
|
import styles from './StepDatePicker.module.css'
|
||||||
|
|
||||||
|
type Step = 'year' | 'month' | 'day'
|
||||||
|
|
||||||
|
interface StepDatePickerProps {
|
||||||
|
id?: string
|
||||||
|
value: string
|
||||||
|
onChange: (isoDate: string) => void
|
||||||
|
disabled?: boolean
|
||||||
|
placeholder?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const MONTHS = [
|
||||||
|
'Jan',
|
||||||
|
'Feb',
|
||||||
|
'Mar',
|
||||||
|
'Apr',
|
||||||
|
'May',
|
||||||
|
'Jun',
|
||||||
|
'Jul',
|
||||||
|
'Aug',
|
||||||
|
'Sep',
|
||||||
|
'Oct',
|
||||||
|
'Nov',
|
||||||
|
'Dec',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
function parseIsoDate(value: string | null | undefined): {
|
||||||
|
year: number
|
||||||
|
month: number
|
||||||
|
day: number
|
||||||
|
} | null {
|
||||||
|
if (!value?.trim()) return null
|
||||||
|
const match = value.trim().match(/^(\d{4})-(\d{2})-(\d{2})/)
|
||||||
|
if (!match) return null
|
||||||
|
const year = Number(match[1])
|
||||||
|
const month = Number(match[2])
|
||||||
|
const day = Number(match[3])
|
||||||
|
if (!year || month < 1 || month > 12 || day < 1 || day > 31) return null
|
||||||
|
const probe = new Date(year, month - 1, day)
|
||||||
|
if (
|
||||||
|
probe.getFullYear() !== year ||
|
||||||
|
probe.getMonth() !== month - 1 ||
|
||||||
|
probe.getDate() !== day
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return { year, month, day }
|
||||||
|
}
|
||||||
|
|
||||||
|
function toIsoDate(year: number, month: number, day: number): string {
|
||||||
|
return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDisplayDate(iso: string): string {
|
||||||
|
const parsed = parseIsoDate(iso)
|
||||||
|
if (!parsed) return ''
|
||||||
|
return new Date(parsed.year, parsed.month - 1, parsed.day).toLocaleDateString(
|
||||||
|
'en-US',
|
||||||
|
{ month: 'short', day: 'numeric', year: 'numeric' },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function daysInMonth(year: number, month: number): number {
|
||||||
|
return new Date(year, month, 0).getDate()
|
||||||
|
}
|
||||||
|
|
||||||
|
function startWeekday(year: number, month: number): number {
|
||||||
|
return new Date(year, month - 1, 1).getDay()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StepDatePicker({
|
||||||
|
id,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
disabled = false,
|
||||||
|
placeholder = 'Select date',
|
||||||
|
}: StepDatePickerProps) {
|
||||||
|
const autoId = useId()
|
||||||
|
const fieldId = id ?? autoId
|
||||||
|
const rootRef = useRef<HTMLDivElement>(null)
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [step, setStep] = useState<Step>('year')
|
||||||
|
|
||||||
|
const today = useMemo(() => {
|
||||||
|
const now = new Date()
|
||||||
|
return {
|
||||||
|
year: now.getFullYear(),
|
||||||
|
month: now.getMonth() + 1,
|
||||||
|
day: now.getDate(),
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const selected = parseIsoDate(value)
|
||||||
|
const [draftYear, setDraftYear] = useState(selected?.year ?? today.year)
|
||||||
|
const [draftMonth, setDraftMonth] = useState(selected?.month ?? today.month)
|
||||||
|
|
||||||
|
const years = useMemo(() => {
|
||||||
|
const start = today.year - 8
|
||||||
|
const end = today.year + 12
|
||||||
|
return Array.from({ length: end - start + 1 }, (_, i) => start + i)
|
||||||
|
}, [today.year])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return
|
||||||
|
const onPointerDown = (event: MouseEvent) => {
|
||||||
|
if (!rootRef.current?.contains(event.target as Node)) {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Escape') setOpen(false)
|
||||||
|
}
|
||||||
|
document.addEventListener('mousedown', onPointerDown)
|
||||||
|
document.addEventListener('keydown', onKeyDown)
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', onPointerDown)
|
||||||
|
document.removeEventListener('keydown', onKeyDown)
|
||||||
|
}
|
||||||
|
}, [open])
|
||||||
|
|
||||||
|
function openPicker() {
|
||||||
|
if (disabled) return
|
||||||
|
const initial = selected ?? today
|
||||||
|
setDraftYear(initial.year)
|
||||||
|
setDraftMonth(initial.month)
|
||||||
|
setStep('year')
|
||||||
|
setOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickYear(year: number) {
|
||||||
|
setDraftYear(year)
|
||||||
|
setStep('month')
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickMonth(month: number) {
|
||||||
|
setDraftMonth(month)
|
||||||
|
setStep('day')
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickDay(day: number) {
|
||||||
|
onChange(toIsoDate(draftYear, draftMonth, day))
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const display = value ? formatDisplayDate(value) : ''
|
||||||
|
const dayCount = daysInMonth(draftYear, draftMonth)
|
||||||
|
const padStart = startWeekday(draftYear, draftMonth)
|
||||||
|
const highlightDay =
|
||||||
|
selected && selected.year === draftYear && selected.month === draftMonth
|
||||||
|
? selected.day
|
||||||
|
: draftYear === today.year && draftMonth === today.month
|
||||||
|
? today.day
|
||||||
|
: null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root} ref={rootRef}>
|
||||||
|
<button
|
||||||
|
id={fieldId}
|
||||||
|
type="button"
|
||||||
|
className={`${styles.trigger} ${open ? styles.triggerOpen : ''} ${
|
||||||
|
!display ? styles.triggerPlaceholder : ''
|
||||||
|
}`}
|
||||||
|
onClick={() => (open ? setOpen(false) : openPicker())}
|
||||||
|
disabled={disabled}
|
||||||
|
aria-haspopup="dialog"
|
||||||
|
aria-expanded={open}
|
||||||
|
>
|
||||||
|
<span className={styles.triggerText}>{display || placeholder}</span>
|
||||||
|
<CalendarDays size={16} className={styles.icon} aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{open ? (
|
||||||
|
<div className={styles.panel} role="dialog" aria-label="Choose date">
|
||||||
|
<div className={styles.panelHeader}>
|
||||||
|
{step !== 'year' ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles.backBtn}
|
||||||
|
onClick={() => setStep(step === 'day' ? 'month' : 'year')}
|
||||||
|
aria-label="Back"
|
||||||
|
>
|
||||||
|
<ChevronLeft size={16} />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<span className={styles.backSpacer} />
|
||||||
|
)}
|
||||||
|
<div className={styles.steps}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`${styles.stepChip} ${step === 'year' ? styles.stepActive : ''}`}
|
||||||
|
onClick={() => setStep('year')}
|
||||||
|
>
|
||||||
|
{draftYear}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`${styles.stepChip} ${step === 'month' ? styles.stepActive : ''}`}
|
||||||
|
onClick={() => setStep('month')}
|
||||||
|
>
|
||||||
|
{MONTHS[draftMonth - 1]}
|
||||||
|
</button>
|
||||||
|
<span
|
||||||
|
className={`${styles.stepChip} ${step === 'day' ? styles.stepActive : ''} ${styles.stepStatic}`}
|
||||||
|
>
|
||||||
|
Day
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{step === 'year' ? (
|
||||||
|
<div className={styles.yearList}>
|
||||||
|
{years.map((year) => (
|
||||||
|
<button
|
||||||
|
key={year}
|
||||||
|
type="button"
|
||||||
|
className={`${styles.optionBtn} ${
|
||||||
|
year === draftYear ? styles.optionSelected : ''
|
||||||
|
} ${year === today.year ? styles.optionToday : ''}`}
|
||||||
|
onClick={() => pickYear(year)}
|
||||||
|
>
|
||||||
|
{year}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{step === 'month' ? (
|
||||||
|
<div className={styles.monthGrid}>
|
||||||
|
{MONTHS.map((label, index) => {
|
||||||
|
const month = index + 1
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={label}
|
||||||
|
type="button"
|
||||||
|
className={`${styles.optionBtn} ${
|
||||||
|
month === draftMonth ? styles.optionSelected : ''
|
||||||
|
} ${
|
||||||
|
draftYear === today.year && month === today.month
|
||||||
|
? styles.optionToday
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
onClick={() => pickMonth(month)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{step === 'day' ? (
|
||||||
|
<div className={styles.dayPanel}>
|
||||||
|
<div className={styles.weekdayRow}>
|
||||||
|
{['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].map((d) => (
|
||||||
|
<span key={d} className={styles.weekday}>
|
||||||
|
{d}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className={styles.dayGrid}>
|
||||||
|
{Array.from({ length: padStart }, (_, i) => (
|
||||||
|
<span key={`pad-${i}`} className={styles.dayEmpty} />
|
||||||
|
))}
|
||||||
|
{Array.from({ length: dayCount }, (_, i) => {
|
||||||
|
const day = i + 1
|
||||||
|
const isSelected =
|
||||||
|
selected?.year === draftYear &&
|
||||||
|
selected?.month === draftMonth &&
|
||||||
|
selected?.day === day
|
||||||
|
const isToday =
|
||||||
|
draftYear === today.year &&
|
||||||
|
draftMonth === today.month &&
|
||||||
|
day === today.day
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={day}
|
||||||
|
type="button"
|
||||||
|
className={`${styles.dayBtn} ${
|
||||||
|
isSelected ? styles.daySelected : ''
|
||||||
|
} ${isToday && !isSelected ? styles.dayToday : ''} ${
|
||||||
|
highlightDay === day && !isSelected ? styles.dayPrefill : ''
|
||||||
|
}`}
|
||||||
|
onClick={() => pickDay(day)}
|
||||||
|
>
|
||||||
|
{day}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -563,7 +563,7 @@
|
|||||||
gap: 2px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.renewalYear {
|
.renewalDate {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import {
|
|||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
|
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
|
||||||
import { BusinessActionsOverflowMenu } from '../components/BusinessActionsOverflowMenu'
|
import { BusinessActionsOverflowMenu } from '../components/BusinessActionsOverflowMenu'
|
||||||
|
import { MultiSelectDropdown } from '../components/MultiSelectDropdown'
|
||||||
|
import { StepDatePicker } from '../components/StepDatePicker'
|
||||||
import { ToggleSwitch } from '../components/ToggleSwitch'
|
import { ToggleSwitch } from '../components/ToggleSwitch'
|
||||||
import { formatCellForDisplay, toE164CellNumber } from '../lib/cellNumber'
|
import { formatCellForDisplay, toE164CellNumber } from '../lib/cellNumber'
|
||||||
import type {
|
import type {
|
||||||
@@ -45,7 +47,6 @@ import {
|
|||||||
import { listBusinessCategories } from '../services/categoryService'
|
import { listBusinessCategories } from '../services/categoryService'
|
||||||
import { listUserTitles, type UserTitleOption } from '../services/userTitleService'
|
import { listUserTitles, type UserTitleOption } from '../services/userTitleService'
|
||||||
import { Modal } from '../components/Modal'
|
import { Modal } from '../components/Modal'
|
||||||
import { MultiSelectDropdown } from '../components/MultiSelectDropdown'
|
|
||||||
import { PrimaryColorSwatchControl } from '../components/PrimaryColorSwatchControl'
|
import { PrimaryColorSwatchControl } from '../components/PrimaryColorSwatchControl'
|
||||||
import { useToast } from '../context/ToastContext'
|
import { useToast } from '../context/ToastContext'
|
||||||
import {
|
import {
|
||||||
@@ -148,11 +149,11 @@ function formatRenewalCell(
|
|||||||
) {
|
) {
|
||||||
const contractAt = annualRenewalAt || createdAt || null
|
const contractAt = annualRenewalAt || createdAt || null
|
||||||
if (!contractAt) {
|
if (!contractAt) {
|
||||||
return { yearLabel: '—', daysLabel: null as string | null, tone: null as 'ok' | 'warn' | 'danger' | null }
|
return { dateLabel: '—', daysLabel: null as string | null, tone: null as 'ok' | 'warn' | 'danger' | null }
|
||||||
}
|
}
|
||||||
const next = nextAnnualRenewal(contractAt)
|
const next = nextAnnualRenewal(contractAt)
|
||||||
if (!next) {
|
if (!next) {
|
||||||
return { yearLabel: '—', daysLabel: null, tone: null }
|
return { dateLabel: '—', daysLabel: null, tone: null }
|
||||||
}
|
}
|
||||||
const daysLabel =
|
const daysLabel =
|
||||||
next.daysRemaining === 0
|
next.daysRemaining === 0
|
||||||
@@ -161,7 +162,11 @@ function formatRenewalCell(
|
|||||||
? '1 day left'
|
? '1 day left'
|
||||||
: `${next.daysRemaining} days left`
|
: `${next.daysRemaining} days left`
|
||||||
return {
|
return {
|
||||||
yearLabel: String(next.year),
|
dateLabel: next.next.toLocaleDateString('en-US', {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
}),
|
||||||
daysLabel,
|
daysLabel,
|
||||||
tone: renewalTone(next.daysRemaining),
|
tone: renewalTone(next.daysRemaining),
|
||||||
}
|
}
|
||||||
@@ -1294,7 +1299,7 @@ export function BusinessesPage() {
|
|||||||
<td className={styles.td}>{b.name}</td>
|
<td className={styles.td}>{b.name}</td>
|
||||||
<td className={styles.td}>
|
<td className={styles.td}>
|
||||||
<div className={styles.renewalCell}>
|
<div className={styles.renewalCell}>
|
||||||
<span className={styles.renewalYear}>{renewal.yearLabel}</span>
|
<span className={styles.renewalDate}>{renewal.dateLabel}</span>
|
||||||
{renewal.daysLabel ? (
|
{renewal.daysLabel ? (
|
||||||
<span
|
<span
|
||||||
className={`${styles.renewalDays} ${
|
className={`${styles.renewalDays} ${
|
||||||
@@ -1532,12 +1537,12 @@ export function BusinessesPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.field}>
|
<div className={styles.field}>
|
||||||
<label htmlFor="edit-annual-renewal">Annual renewal</label>
|
<label htmlFor="edit-annual-renewal">Annual renewal</label>
|
||||||
<input
|
<StepDatePicker
|
||||||
id="edit-annual-renewal"
|
id="edit-annual-renewal"
|
||||||
type="date"
|
|
||||||
value={editAnnualRenewalAt}
|
value={editAnnualRenewalAt}
|
||||||
onChange={(e) => setEditAnnualRenewalAt(e.target.value)}
|
onChange={setEditAnnualRenewalAt}
|
||||||
disabled={editSubmitting}
|
disabled={editSubmitting}
|
||||||
|
placeholder="Select renewal date"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.field}>
|
<div className={styles.field}>
|
||||||
@@ -2077,12 +2082,12 @@ export function BusinessesPage() {
|
|||||||
|
|
||||||
<div className={`${styles.field} ${styles.formCol4}`}>
|
<div className={`${styles.field} ${styles.formCol4}`}>
|
||||||
<label htmlFor="create-annual-renewal">Annual renewal</label>
|
<label htmlFor="create-annual-renewal">Annual renewal</label>
|
||||||
<input
|
<StepDatePicker
|
||||||
id="create-annual-renewal"
|
id="create-annual-renewal"
|
||||||
type="date"
|
|
||||||
value={createAnnualRenewalAt}
|
value={createAnnualRenewalAt}
|
||||||
onChange={(e) => setCreateAnnualRenewalAt(e.target.value)}
|
onChange={setCreateAnnualRenewalAt}
|
||||||
disabled={createSubmitting}
|
disabled={createSubmitting}
|
||||||
|
placeholder="Select renewal date"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user