TimePeriodSelect.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import { useState } from 'react';
  2. import { CalendarIcon } from 'lucide-react';
  3. import type { FunnelPeriod } from '../../../api/types';
  4. import { toSnapshotParam, yesterday } from '../period';
  5. import { Calendar } from '@/components/ui/calendar';
  6. import {
  7. Popover,
  8. PopoverContent,
  9. PopoverTrigger,
  10. } from '@/components/ui/popover';
  11. import { cn } from '@/lib/utils';
  12. interface Props {
  13. period: FunnelPeriod;
  14. /** Selected day for `period: 'day'`; `null` = latest available day. */
  15. snapshotDt: Date | null;
  16. /** Pick a historical day → switch to single-day mode. */
  17. onPickDate: (d: Date) => void;
  18. /** Reset single-day back to the latest available day. */
  19. onPickLatest: () => void;
  20. /** Pick a rolling window. */
  21. onPickRolling: (p: 'last_7d' | 'last_30d') => void;
  22. }
  23. // 统一 segmented:选中态由 React 状态直接驱动 mint 实色,不依赖 shadcn 变体。
  24. const SEG =
  25. 'h-8 px-3 inline-flex items-center gap-1.5 rounded-md text-sm font-medium transition-colors cursor-pointer';
  26. const ON = 'bg-primary text-primary-foreground shadow-sm';
  27. const OFF = 'text-muted-foreground hover:text-foreground hover:bg-background';
  28. /**
  29. * 时间选择 — 单日 / 近 7 天 / 近 30 天 三选一的 segmented 控件(docs/04)。
  30. * 「单日」段点开日历可选历史日(上限昨日,T+1);任一选中 = mint 高亮,
  31. * 给明确的"当前在此周期"反馈。
  32. */
  33. export function TimePeriodSelect({
  34. period,
  35. snapshotDt,
  36. onPickDate,
  37. onPickLatest,
  38. onPickRolling,
  39. }: Props) {
  40. const max = yesterday();
  41. const [open, setOpen] = useState(false);
  42. return (
  43. <div className="inline-flex items-center gap-1 rounded-lg border border-border bg-muted/60 p-1">
  44. {/* 单日 —— 日历 popover;未选具体日 = 最新可用日 */}
  45. <Popover open={open} onOpenChange={setOpen}>
  46. <PopoverTrigger asChild>
  47. <button
  48. type="button"
  49. aria-label="选择历史日期"
  50. aria-pressed={period === 'day'}
  51. className={cn(SEG, period === 'day' ? ON : OFF)}
  52. >
  53. <CalendarIcon className="size-4" />
  54. <span>单日</span>
  55. <span className="tabular-nums opacity-90">
  56. {snapshotDt ? toSnapshotParam(snapshotDt) : '最新'}
  57. </span>
  58. </button>
  59. </PopoverTrigger>
  60. <PopoverContent className="w-auto p-0" align="start">
  61. {/* 最新可用日快捷项:回到默认(后端取 MAX(dt)) */}
  62. <button
  63. type="button"
  64. onClick={() => {
  65. onPickLatest();
  66. setOpen(false);
  67. }}
  68. className={cn(
  69. 'w-full px-3 py-2 text-left text-sm border-b border-border transition-colors',
  70. !snapshotDt
  71. ? 'text-primary font-medium'
  72. : 'text-foreground hover:bg-muted',
  73. )}
  74. >
  75. 最新可用日
  76. </button>
  77. <Calendar
  78. mode="single"
  79. selected={snapshotDt ?? undefined}
  80. defaultMonth={snapshotDt ?? max}
  81. disabled={{ after: max }}
  82. onSelect={(d) => {
  83. if (d) {
  84. onPickDate(d);
  85. setOpen(false);
  86. }
  87. }}
  88. />
  89. </PopoverContent>
  90. </Popover>
  91. {/* 近 7 天 / 近 30 天 */}
  92. <button
  93. type="button"
  94. aria-pressed={period === 'last_7d'}
  95. onClick={() => onPickRolling('last_7d')}
  96. className={cn(SEG, period === 'last_7d' ? ON : OFF)}
  97. >
  98. 近 7 天
  99. </button>
  100. <button
  101. type="button"
  102. aria-pressed={period === 'last_30d'}
  103. onClick={() => onPickRolling('last_30d')}
  104. className={cn(SEG, period === 'last_30d' ? ON : OFF)}
  105. >
  106. 近 30 天
  107. </button>
  108. </div>
  109. );
  110. }
  111. export default TimePeriodSelect;