SearchDateRangePicker.jsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. "use client";
  2. import React from "react";
  3. import { CalendarRange } from "lucide-react";
  4. import { formatIsoDateRangeLabelDe } from "@/lib/frontend/search/dateRange";
  5. import { Button } from "@/components/ui/button";
  6. import { Input } from "@/components/ui/input";
  7. import { Label } from "@/components/ui/label";
  8. import {
  9. Popover,
  10. PopoverContent,
  11. PopoverTrigger,
  12. } from "@/components/ui/popover";
  13. function toNullableYmd(value) {
  14. if (typeof value !== "string") return null;
  15. const s = value.trim();
  16. return s ? s : null;
  17. }
  18. export default function SearchDateRangePicker({
  19. from,
  20. to,
  21. onDateRangeChange,
  22. isSubmitting,
  23. }) {
  24. const [open, setOpen] = React.useState(false);
  25. const label = formatIsoDateRangeLabelDe({ from, to }) || "Zeitraum";
  26. const fromId = React.useId();
  27. const toId = React.useId();
  28. const canClear = Boolean((from || to) && !isSubmitting);
  29. return (
  30. <div className="grid gap-2">
  31. <Label>Zeitraum</Label>
  32. <Popover open={open} onOpenChange={setOpen}>
  33. <PopoverTrigger asChild>
  34. <Button
  35. type="button"
  36. variant="outline"
  37. disabled={isSubmitting}
  38. title="Zeitraum auswählen"
  39. className="justify-between"
  40. >
  41. <span className="truncate">{label}</span>
  42. <CalendarRange className="h-4 w-4 opacity-70" />
  43. </Button>
  44. </PopoverTrigger>
  45. <PopoverContent align="start" className="w-80">
  46. <div className="space-y-4">
  47. <div className="grid gap-2">
  48. <Label htmlFor={fromId}>Von</Label>
  49. <Input
  50. id={fromId}
  51. type="date"
  52. value={from || ""}
  53. disabled={isSubmitting}
  54. onChange={(e) => {
  55. if (typeof onDateRangeChange !== "function") return;
  56. onDateRangeChange({
  57. from: toNullableYmd(e.target.value),
  58. to: to ?? null,
  59. });
  60. }}
  61. />
  62. </div>
  63. <div className="grid gap-2">
  64. <Label htmlFor={toId}>Bis</Label>
  65. <Input
  66. id={toId}
  67. type="date"
  68. value={to || ""}
  69. disabled={isSubmitting}
  70. onChange={(e) => {
  71. if (typeof onDateRangeChange !== "function") return;
  72. onDateRangeChange({
  73. from: from ?? null,
  74. to: toNullableYmd(e.target.value),
  75. });
  76. }}
  77. />
  78. </div>
  79. <div className="flex justify-end gap-2">
  80. <Button
  81. type="button"
  82. variant="outline"
  83. size="sm"
  84. disabled={!canClear}
  85. onClick={() => {
  86. if (!canClear) return;
  87. if (typeof onDateRangeChange !== "function") return;
  88. onDateRangeChange({ from: null, to: null });
  89. }}
  90. title="Zeitraum entfernen"
  91. >
  92. Zurücksetzen
  93. </Button>
  94. </div>
  95. <div className="text-xs text-muted-foreground">
  96. Tipp: Für einen einzelnen Tag setzen Sie <strong>Von</strong> und{" "}
  97. <strong>Bis</strong> auf dasselbe Datum.
  98. </div>
  99. </div>
  100. </PopoverContent>
  101. </Popover>
  102. </div>
  103. );
  104. }