@@ -47,6 +47,68 @@ const DAY_LABELS = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
// ── Doormile-themed range calendar ───────────────────────────────────────────────
// ── Doormile-themed range calendar ───────────────────────────────────────────────
// Click a day to set the start, click again to set the end (auto-swaps if reversed).
// Click a day to set the start, click again to set the end (auto-swaps if reversed).
// In-range days get a soft red wash; the two endpoints are solid brand red.
// In-range days get a soft red wash; the two endpoints are solid brand red.
// Renders two compact months side-by-side (single month on small screens).
// A single compact month grid — kept lightweight so both months render fast.
function MonthGrid ( { view , start , end , max , onDay } ) {
const gridStart = view . startOf ( 'month' ) . subtract ( view . startOf ( 'month' ) . day ( ) , 'day' ) ;
const cells = Array . from ( { length : 42 } , ( _ , i ) => gridStart . add ( i , 'day' ) ) ;
return (
< Box sx = { { width : 224 } } >
< Typography align = "center" sx = { { fontWeight : 700 , fontSize : '0.82rem' , color : '#212529' , mb : 0.75 } } >
{ view . format ( 'MMMM YYYY' ) }
< / Typography >
{ /* Weekday labels */ }
< Box sx = { { display : 'grid' , gridTemplateColumns : 'repeat(7, 1fr)' , mb : 0.25 } } >
{ DAY _LABELS . map ( ( d , i ) => (
< Typography key = { i } align = "center" sx = { { fontSize : '0.62rem' , fontWeight : 600 , color : '#B0B5BA' , py : 0.25 } } >
{ d }
< / Typography >
) ) }
< / Box >
{ /* Days */ }
< Box sx = { { display : 'grid' , gridTemplateColumns : 'repeat(7, 1fr)' } } >
{ cells . map ( ( d ) => {
const inMonth = d . month ( ) === view . month ( ) ;
const isStart = start && d . isSame ( start , 'day' ) ;
const isEnd = end && d . isSame ( end , 'day' ) ;
const isEndpoint = isStart || isEnd ;
const inRange = start && end && d . isAfter ( start , 'day' ) && d . isBefore ( end , 'day' ) ;
const isToday = d . isSame ( dayjs ( ) , 'day' ) ;
const disabled = max && d . isAfter ( max , 'day' ) ;
return (
< Box key = { d . format ( DATE _FMT ) } sx = { { display : 'flex' , justifyContent : 'center' , bgcolor : inRange ? alpha ( BRAND , 0.07 ) : 'transparent' ,
borderTopLeftRadius : isStart ? 6 : 0 , borderBottomLeftRadius : isStart ? 6 : 0 ,
borderTopRightRadius : isEnd ? 6 : 0 , borderBottomRightRadius : isEnd ? 6 : 0 } } >
< Box
component = "button"
type = "button"
disabled = { disabled }
onClick = { ( ) => onDay ( d ) }
sx = { {
width : 28 , height : 28 , m : '1px' , border : 'none' , cursor : disabled ? 'default' : 'pointer' ,
borderRadius : 1.5 , fontSize : '0.72rem' , fontFamily : 'inherit' ,
fontWeight : isEndpoint ? 700 : 500 ,
color : disabled ? '#D5D9DD' : isEndpoint ? '#fff' : inMonth ? '#3C4043' : '#C4C9CE' ,
bgcolor : isEndpoint ? BRAND : 'transparent' ,
boxShadow : isToday && ! isEndpoint ? ` inset 0 0 0 1.5px ${ BRAND } ` : 'none' ,
transition : 'background-color .12s' ,
'&:hover' : { bgcolor : disabled ? 'transparent' : isEndpoint ? '#9E0E20' : alpha ( BRAND , 0.1 ) }
} }
>
{ d . date ( ) }
< / Box >
< / Box >
) ;
} ) }
< / Box >
< / Box >
) ;
}
function RangeCalendar ( { from , to , maxDate , onSelect } ) {
function RangeCalendar ( { from , to , maxDate , onSelect } ) {
const [ view , setView ] = useState ( dayjs ( to || from || undefined ) . startOf ( 'month' ) ) ;
const [ view , setView ] = useState ( dayjs ( to || from || undefined ) . startOf ( 'month' ) ) ;
const [ anchorDate , setAnchorDate ] = useState ( null ) ; // first click while picking a new range
const [ anchorDate , setAnchorDate ] = useState ( null ) ; // first click while picking a new range
@@ -55,9 +117,6 @@ function RangeCalendar({ from, to, maxDate, onSelect }) {
const end = to ? dayjs ( to ) : null ;
const end = to ? dayjs ( to ) : null ;
const max = maxDate ? dayjs ( maxDate ) : null ;
const max = maxDate ? dayjs ( maxDate ) : null ;
const gridStart = view . startOf ( 'month' ) . subtract ( view . startOf ( 'month' ) . day ( ) , 'day' ) ;
const cells = Array . from ( { length : 42 } , ( _ , i ) => gridStart . add ( i , 'day' ) ) ;
const handleDay = ( d ) => {
const handleDay = ( d ) => {
if ( ! anchorDate ) {
if ( ! anchorDate ) {
setAnchorDate ( d ) ;
setAnchorDate ( d ) ;
@@ -72,63 +131,23 @@ function RangeCalendar({ from, to, maxDate, onSelect }) {
} ;
} ;
return (
return (
< Box sx = { { p : 2 , width : 320 } } >
< Box sx = { { px : 1.5 , py : 1.5 } } >
{ /* Month header */ }
{ /* Shared nav header spanning both months */ }
< Stack direction = "row" alignItems = "center" justifyContent = "space-between" sx = { { mb : 1.5 } } >
< Stack direction = "row" alignItems = "center" justifyContent = "space-between" sx = { { mb : 1 } } >
< IconButton size = "small" onClick = { ( ) => setView ( ( v ) => v . subtract ( 1 , 'month' ) ) } sx = { { color : '#5F6368' } } >
< IconButton size = "small" onClick = { ( ) => setView ( ( v ) => v . subtract ( 1 , 'month' ) ) } sx = { { color : '#9AA0A6' , p : 0.5 } } >
< ChevronLeftRoundedIcon / >
< ChevronLeftRoundedIcon fontSize = "small" / >
< / IconButton >
< / IconButton >
< Typography sx = { { fontWeight : 800 , color : '#212529' } } > { view . format ( 'MMMM YYYY' ) } < / Typography >
< IconButton size = "small" onClick = { ( ) => setView ( ( v ) => v . add ( 1 , 'month' ) ) } sx = { { color : '#9AA0A6' , p : 0.5 } } >
< IconButton size = "small" onClick = { ( ) => setView ( ( v ) => v . add ( 1 , 'month' ) ) } sx = { { color : '#5F6368' } } >
< ChevronRightRoundedIcon fontSize = "small" / >
< ChevronRightRoundedIcon / >
< / IconButton >
< / IconButton >
< / Stack >
< / Stack >
{ /* Weekday labels */ }
< Stack direction = "row" spacing = { 2 } >
< Box sx = { { display : 'grid' , gridTemplateColumns : 'repeat(7, 1fr)' , mb : 0.5 } } >
< MonthGrid view = { view } start = { start } end = { end } max = { max } onDay = { handleDay } / >
{ DAY _LABELS . map ( ( d , i ) => (
< Box sx = { { display : { xs : 'none' , sm : 'block' } } } >
< Typography key = { i } align = "center" sx = { { fontSize : '0.72rem' , fontWeight : 700 , color : '#9AA0A6' , py : 0.5 } } >
< MonthGrid view = { view . add ( 1 , 'month' ) } start = { start } end = { end } max = { max } onDay = { handleDay } / >
{ d }
< / Box >
< / Typography >
< / Stack >
) ) }
< / Box >
{ /* Days */ }
< Box sx = { { display : 'grid' , gridTemplateColumns : 'repeat(7, 1fr)' , rowGap : 0.25 } } >
{ cells . map ( ( d ) => {
const inMonth = d . month ( ) === view . month ( ) ;
const isStart = start && d . isSame ( start , 'day' ) ;
const isEnd = end && d . isSame ( end , 'day' ) ;
const isEndpoint = isStart || isEnd ;
const inRange = start && end && d . isAfter ( start , 'day' ) && d . isBefore ( end , 'day' ) ;
const isToday = d . isSame ( dayjs ( ) , 'day' ) ;
const disabled = max && d . isAfter ( max , 'day' ) ;
return (
< Box key = { d . format ( DATE _FMT ) } sx = { { display : 'flex' , justifyContent : 'center' , bgcolor : inRange ? alpha ( BRAND , 0.08 ) : 'transparent' ,
borderTopLeftRadius : isStart ? 8 : 0 , borderBottomLeftRadius : isStart ? 8 : 0 ,
borderTopRightRadius : isEnd ? 8 : 0 , borderBottomRightRadius : isEnd ? 8 : 0 } } >
< Box
component = "button"
type = "button"
disabled = { disabled }
onClick = { ( ) => handleDay ( d ) }
sx = { {
width : 36 , height : 36 , m : '2px' , border : 'none' , cursor : disabled ? 'default' : 'pointer' ,
borderRadius : 2 , fontSize : '0.85rem' , fontFamily : 'inherit' ,
fontWeight : isEndpoint ? 800 : 600 ,
color : disabled ? '#CED4DA' : isEndpoint ? '#fff' : inMonth ? '#212529' : '#B9BEC4' ,
bgcolor : isEndpoint ? BRAND : 'transparent' ,
boxShadow : isToday && ! isEndpoint ? ` inset 0 0 0 1.5px ${ BRAND } ` : 'none' ,
transition : 'background-color .15s' ,
'&:hover' : { bgcolor : disabled ? 'transparent' : isEndpoint ? '#9E0E20' : alpha ( BRAND , 0.12 ) }
} }
>
{ d . date ( ) }
< / Box >
< / Box >
) ;
} ) }
< / Box >
< / Box >
< / Box >
) ;
) ;
}
}
@@ -317,11 +336,11 @@ export default function Dashboard() {
onClose = { ( ) => setCalAnchor ( null ) }
onClose = { ( ) => setCalAnchor ( null ) }
anchorOrigin = { { vertical : 'bottom' , horizontal : 'right' } }
anchorOrigin = { { vertical : 'bottom' , horizontal : 'right' } }
transformOrigin = { { vertical : 'top' , horizontal : 'right' } }
transformOrigin = { { vertical : 'top' , horizontal : 'right' } }
PaperProps = { { sx : { mt : 1 , borderRadius : 3 , border : '1px solid #ECEEF1 ' , boxShadow : '0 12 px 40 px rgba(0,0,0,0.14 )' , overflow : 'hidden' } } }
PaperProps = { { sx : { mt : 1 , borderRadius : 2.5 , border : '1px solid #EEF0F2 ' , boxShadow : '0 8 px 28 px rgba(0,0,0,0.10 )' , overflow : 'hidden' } } }
>
>
< Box sx = { { px : 2 , pt : 2 } } >
< Box sx = { { px : 2 , pt : 1.75 , pb : 0.5 } } >
< Typography sx = { { fontWeight : 8 00, color : '#212529' } } > Select date range < / Typography >
< Typography sx = { { fontWeight : 7 00, fontSize : '0.9rem' , color : '#212529' } } > Select date range < / Typography >
< Typography variant = "caption" color = "text.secondary" >
< Typography variant = "caption" sx = { { color : '#8A9099' } } >
{ dayjs ( range . from ) . format ( 'DD MMM' ) } – { dayjs ( range . to ) . format ( 'DD MMM YYYY' ) } · { dayCount } { dayCount === 1 ? 'day' : 'days' }
{ dayjs ( range . from ) . format ( 'DD MMM' ) } – { dayjs ( range . to ) . format ( 'DD MMM YYYY' ) } · { dayCount } { dayCount === 1 ? 'day' : 'days' }
< / Typography >
< / Typography >
< / Box >
< / Box >