Pull out period selection popover into its own component
This commit is contained in:
@@ -7,7 +7,6 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -45,17 +44,11 @@ import {
|
|||||||
import type { TooltipProps } from "recharts";
|
import type { TooltipProps } from "recharts";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import {
|
import { ArrowUpRight, ArrowDownRight, Minus, TrendingUp, AlertCircle } from "lucide-react";
|
||||||
Popover,
|
import PeriodSelectionPopover, {
|
||||||
PopoverContent,
|
type QuickPreset,
|
||||||
PopoverTrigger,
|
} from "@/components/dashboard/PeriodSelectionPopover";
|
||||||
} from "@/components/ui/popover";
|
import type { CustomPeriod, NaturalLanguagePeriodResult } from "@/utils/naturalLanguagePeriod";
|
||||||
import { ArrowUpRight, ArrowDownRight, Minus, TrendingUp, AlertCircle, Calendar } from "lucide-react";
|
|
||||||
import type { CustomPeriod } from "@/utils/naturalLanguagePeriod";
|
|
||||||
import {
|
|
||||||
generateNaturalLanguagePreview,
|
|
||||||
parseNaturalLanguagePeriod,
|
|
||||||
} from "@/utils/naturalLanguagePeriod";
|
|
||||||
|
|
||||||
type TrendDirection = "up" | "down" | "flat";
|
type TrendDirection = "up" | "down" | "flat";
|
||||||
|
|
||||||
@@ -634,8 +627,6 @@ const FinancialOverview = () => {
|
|||||||
});
|
});
|
||||||
const [isLast30DaysMode, setIsLast30DaysMode] = useState<boolean>(true);
|
const [isLast30DaysMode, setIsLast30DaysMode] = useState<boolean>(true);
|
||||||
const [isPeriodPopoverOpen, setIsPeriodPopoverOpen] = useState<boolean>(false);
|
const [isPeriodPopoverOpen, setIsPeriodPopoverOpen] = useState<boolean>(false);
|
||||||
const [naturalLanguageInput, setNaturalLanguageInput] = useState<string>("");
|
|
||||||
const [showSuggestions, setShowSuggestions] = useState<boolean>(false);
|
|
||||||
const [metrics, setMetrics] = useState<Record<ChartSeriesKey, boolean>>({
|
const [metrics, setMetrics] = useState<Record<ChartSeriesKey, boolean>>({
|
||||||
income: true,
|
income: true,
|
||||||
cogs: true,
|
cogs: true,
|
||||||
@@ -1040,62 +1031,19 @@ const FinancialOverview = () => {
|
|||||||
[series]: !prev[series],
|
[series]: !prev[series],
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
const handleNaturalLanguageResult = (result: NaturalLanguagePeriodResult) => {
|
||||||
|
if (result === "last30days") {
|
||||||
const suggestions = [
|
|
||||||
"last 30 days",
|
|
||||||
"this month",
|
|
||||||
"last month",
|
|
||||||
"this quarter",
|
|
||||||
"last quarter",
|
|
||||||
"this year",
|
|
||||||
"last year",
|
|
||||||
"last 3 months",
|
|
||||||
"last 6 months",
|
|
||||||
"last 2 quarters",
|
|
||||||
"Q1 2024",
|
|
||||||
"q1-q3 24",
|
|
||||||
"q1 24 - q2 25",
|
|
||||||
"January 2024",
|
|
||||||
"jan-24",
|
|
||||||
"jan-may 24",
|
|
||||||
"2023",
|
|
||||||
"2021-2023",
|
|
||||||
"21-23",
|
|
||||||
"January to March 2024",
|
|
||||||
"jan 2023 - may 2024"
|
|
||||||
];
|
|
||||||
|
|
||||||
const filteredSuggestions = suggestions.filter(suggestion =>
|
|
||||||
suggestion.toLowerCase().includes(naturalLanguageInput.toLowerCase()) &&
|
|
||||||
suggestion.toLowerCase() !== naturalLanguageInput.toLowerCase()
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleNaturalLanguageChange = (value: string) => {
|
|
||||||
setNaturalLanguageInput(value);
|
|
||||||
setShowSuggestions(value.length > 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNaturalLanguageSubmit = (input: string) => {
|
|
||||||
const parsed = parseNaturalLanguagePeriod(input, currentDate);
|
|
||||||
if (parsed === "last30days") {
|
|
||||||
setIsLast30DaysMode(true);
|
setIsLast30DaysMode(true);
|
||||||
setIsPeriodPopoverOpen(false);
|
return;
|
||||||
} else if (parsed) {
|
}
|
||||||
setIsLast30DaysMode(false);
|
|
||||||
setCustomPeriod(parsed);
|
if (result) {
|
||||||
setIsPeriodPopoverOpen(false);
|
setIsLast30DaysMode(false);
|
||||||
|
setCustomPeriod(result);
|
||||||
}
|
}
|
||||||
setNaturalLanguageInput("");
|
|
||||||
setShowSuggestions(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSuggestionClick = (suggestion: string) => {
|
const handleQuickPeriod = (preset: QuickPreset) => {
|
||||||
setNaturalLanguageInput(suggestion);
|
|
||||||
handleNaturalLanguageSubmit(suggestion);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleQuickPeriod = (preset: string) => {
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const currentYear = now.getFullYear();
|
const currentYear = now.getFullYear();
|
||||||
const currentMonth = now.getMonth();
|
const currentMonth = now.getMonth();
|
||||||
@@ -1176,175 +1124,16 @@ const FinancialOverview = () => {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{!error && (
|
{!error && (
|
||||||
<>
|
<>
|
||||||
<Popover open={isPeriodPopoverOpen} onOpenChange={setIsPeriodPopoverOpen}>
|
<PeriodSelectionPopover
|
||||||
<PopoverTrigger asChild>
|
open={isPeriodPopoverOpen}
|
||||||
<Button variant="outline" className="h-9">
|
onOpenChange={setIsPeriodPopoverOpen}
|
||||||
<Calendar className="w-4 h-4 mr-2" />
|
selectedLabel={selectedRangeLabel}
|
||||||
{selectedRangeLabel}
|
referenceDate={currentDate}
|
||||||
</Button>
|
isLast30DaysActive={isLast30DaysMode}
|
||||||
</PopoverTrigger>
|
onQuickSelect={handleQuickPeriod}
|
||||||
<PopoverContent className="w-96 p-4" align="end">
|
onApplyResult={handleNaturalLanguageResult}
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="text-sm font-medium">Select Time Period</div>
|
|
||||||
|
|
||||||
{/* Quick Presets - Compact Grid */}
|
|
||||||
<div className="grid grid-cols-3 gap-2">
|
|
||||||
<Button
|
|
||||||
variant={isLast30DaysMode ? "default" : "outline"}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
handleQuickPeriod("last30days");
|
|
||||||
setIsPeriodPopoverOpen(false);
|
|
||||||
}}
|
|
||||||
className="h-8 text-xs"
|
|
||||||
>
|
|
||||||
Last 30 Days
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
handleQuickPeriod("thisMonth");
|
|
||||||
setIsPeriodPopoverOpen(false);
|
|
||||||
}}
|
|
||||||
className="h-8 text-xs"
|
|
||||||
>
|
|
||||||
This Month
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
handleQuickPeriod("lastMonth");
|
|
||||||
setIsPeriodPopoverOpen(false);
|
|
||||||
}}
|
|
||||||
className="h-8 text-xs"
|
|
||||||
>
|
|
||||||
Last Month
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
handleQuickPeriod("thisQuarter");
|
|
||||||
setIsPeriodPopoverOpen(false);
|
|
||||||
}}
|
|
||||||
className="h-8 text-xs"
|
|
||||||
>
|
|
||||||
This Quarter
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
handleQuickPeriod("lastQuarter");
|
|
||||||
setIsPeriodPopoverOpen(false);
|
|
||||||
}}
|
|
||||||
className="h-8 text-xs"
|
|
||||||
>
|
|
||||||
Last Quarter
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
handleQuickPeriod("thisYear");
|
|
||||||
setIsPeriodPopoverOpen(false);
|
|
||||||
}}
|
|
||||||
className="h-8 text-xs"
|
|
||||||
>
|
|
||||||
This Year
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
{/* Natural Language Input */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="text-xs text-muted-foreground">Or type a custom period</div>
|
|
||||||
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
|
||||||
placeholder="e.g., jan-may 24, 2021-2023, Q1-Q3 2024"
|
|
||||||
value={naturalLanguageInput}
|
|
||||||
onChange={(e) => handleNaturalLanguageChange(e.target.value)}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
handleNaturalLanguageSubmit(naturalLanguageInput);
|
|
||||||
}
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
setNaturalLanguageInput("");
|
|
||||||
setShowSuggestions(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="h-8 text-sm"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Live Preview */}
|
|
||||||
{naturalLanguageInput && (
|
|
||||||
<div className="mt-2">
|
|
||||||
{(() => {
|
|
||||||
const parsed = parseNaturalLanguagePeriod(naturalLanguageInput, currentDate);
|
|
||||||
const preview = generateNaturalLanguagePreview(parsed);
|
|
||||||
|
|
||||||
if (preview) {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-2 text-xs">
|
|
||||||
<span className="text-muted-foreground">Recognized as:</span>
|
|
||||||
<span className="font-medium text-green-600 dark:text-green-400">
|
|
||||||
{preview}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<div className="text-xs text-amber-600 dark:text-amber-400">
|
|
||||||
Not recognized - try a different format
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Suggestions Dropdown */}
|
|
||||||
{showSuggestions && filteredSuggestions.length > 0 && (
|
|
||||||
<div className="absolute top-full left-0 right-0 z-50 mt-1 bg-white dark:bg-gray-800 border rounded-md shadow-lg max-h-32 overflow-y-auto">
|
|
||||||
{filteredSuggestions.slice(0, 6).map((suggestion, index) => (
|
|
||||||
<button
|
|
||||||
key={index}
|
|
||||||
className="w-full text-left px-3 py-1.5 text-xs hover:bg-muted transition-colors"
|
|
||||||
onClick={() => handleSuggestionClick(suggestion)}
|
|
||||||
>
|
|
||||||
{suggestion}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Example suggestions when input is empty */}
|
|
||||||
{naturalLanguageInput === "" && (
|
|
||||||
<div className="text-xs text-muted-foreground">
|
|
||||||
<div className="mb-1">Examples:</div>
|
|
||||||
<div className="flex flex-wrap gap-1">
|
|
||||||
{suggestions.slice(0, 6).map((suggestion, index) => (
|
|
||||||
<button
|
|
||||||
key={index}
|
|
||||||
className="px-2 py-0.5 bg-muted hover:bg-muted/80 rounded text-xs transition-colors"
|
|
||||||
onClick={() => handleSuggestionClick(suggestion)}
|
|
||||||
>
|
|
||||||
{suggestion}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
|
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="outline" className="h-9" disabled={loading || !detailRows.length}>
|
<Button variant="outline" className="h-9" disabled={loading || !detailRows.length}>
|
||||||
@@ -1650,7 +1439,7 @@ function FinancialStatGrid({
|
|||||||
}>;
|
}>;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 py-4 max-w-3xl">
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 py-4 w-full">
|
||||||
{cards.map((card) => (
|
{cards.map((card) => (
|
||||||
<FinancialStatCard key={card.key} title={card.title} value={card.value} description={card.description} trend={card.trend} accentClass={card.accentClass} />
|
<FinancialStatCard key={card.key} title={card.title} value={card.value} description={card.description} trend={card.trend} accentClass={card.accentClass} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
269
inventory/src/components/dashboard/PeriodSelectionPopover.tsx
Normal file
269
inventory/src/components/dashboard/PeriodSelectionPopover.tsx
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
import { useMemo, useState, type KeyboardEventHandler } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { Calendar } from "lucide-react";
|
||||||
|
import {
|
||||||
|
generateNaturalLanguagePreview,
|
||||||
|
parseNaturalLanguagePeriod,
|
||||||
|
type NaturalLanguagePeriodResult,
|
||||||
|
} from "@/utils/naturalLanguagePeriod";
|
||||||
|
|
||||||
|
export type QuickPreset =
|
||||||
|
| "last30days"
|
||||||
|
| "thisMonth"
|
||||||
|
| "lastMonth"
|
||||||
|
| "thisQuarter"
|
||||||
|
| "lastQuarter"
|
||||||
|
| "thisYear";
|
||||||
|
|
||||||
|
const SUGGESTIONS = [
|
||||||
|
"last 30 days",
|
||||||
|
"this month",
|
||||||
|
"last month",
|
||||||
|
"this quarter",
|
||||||
|
"last quarter",
|
||||||
|
"this year",
|
||||||
|
"last year",
|
||||||
|
"last 3 months",
|
||||||
|
"last 6 months",
|
||||||
|
"last 2 quarters",
|
||||||
|
"Q1 2024",
|
||||||
|
"q1-q3 24",
|
||||||
|
"q1 24 - q2 25",
|
||||||
|
"January 2024",
|
||||||
|
"jan-24",
|
||||||
|
"jan-may 24",
|
||||||
|
"2023",
|
||||||
|
"2021-2023",
|
||||||
|
"21-23",
|
||||||
|
"January to March 2024",
|
||||||
|
"jan 2023 - may 2024",
|
||||||
|
];
|
||||||
|
|
||||||
|
interface PeriodSelectionPopoverProps {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
selectedLabel: string;
|
||||||
|
referenceDate: Date;
|
||||||
|
isLast30DaysActive: boolean;
|
||||||
|
onQuickSelect: (preset: QuickPreset) => void;
|
||||||
|
onApplyResult: (result: NaturalLanguagePeriodResult) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PeriodSelectionPopover = ({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
selectedLabel,
|
||||||
|
referenceDate,
|
||||||
|
isLast30DaysActive,
|
||||||
|
onQuickSelect,
|
||||||
|
onApplyResult,
|
||||||
|
}: PeriodSelectionPopoverProps) => {
|
||||||
|
const [inputValue, setInputValue] = useState("");
|
||||||
|
const [showSuggestions, setShowSuggestions] = useState(false);
|
||||||
|
|
||||||
|
const filteredSuggestions = useMemo(() => {
|
||||||
|
if (!inputValue) {
|
||||||
|
return SUGGESTIONS;
|
||||||
|
}
|
||||||
|
return SUGGESTIONS.filter((suggestion) =>
|
||||||
|
suggestion.toLowerCase().includes(inputValue.toLowerCase()) &&
|
||||||
|
suggestion.toLowerCase() !== inputValue.toLowerCase()
|
||||||
|
);
|
||||||
|
}, [inputValue]);
|
||||||
|
|
||||||
|
const preview = useMemo(() => {
|
||||||
|
if (!inputValue) {
|
||||||
|
return { label: null, parsed: null } as const;
|
||||||
|
}
|
||||||
|
const parsed = parseNaturalLanguagePeriod(inputValue, referenceDate);
|
||||||
|
return {
|
||||||
|
parsed,
|
||||||
|
label: generateNaturalLanguagePreview(parsed),
|
||||||
|
} as const;
|
||||||
|
}, [inputValue, referenceDate]);
|
||||||
|
|
||||||
|
const closePopover = () => {
|
||||||
|
onOpenChange(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetInput = () => {
|
||||||
|
setInputValue("");
|
||||||
|
setShowSuggestions(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyResult = (value: string) => {
|
||||||
|
const parsed = parseNaturalLanguagePeriod(value, referenceDate);
|
||||||
|
if (!parsed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onApplyResult(parsed);
|
||||||
|
resetInput();
|
||||||
|
closePopover();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (value: string) => {
|
||||||
|
setInputValue(value);
|
||||||
|
setShowSuggestions(value.length > 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
applyResult(inputValue);
|
||||||
|
}
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
resetInput();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSuggestionClick = (suggestion: string) => {
|
||||||
|
setInputValue(suggestion);
|
||||||
|
applyResult(suggestion);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleQuickSelect = (preset: QuickPreset) => {
|
||||||
|
onQuickSelect(preset);
|
||||||
|
resetInput();
|
||||||
|
closePopover();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={onOpenChange}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button variant="outline" className="h-9">
|
||||||
|
<Calendar className="w-4 h-4 mr-2" />
|
||||||
|
{selectedLabel}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-96 p-4" align="end">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="text-sm font-medium">Select Time Period</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-3 gap-2">
|
||||||
|
<Button
|
||||||
|
variant={isLast30DaysActive ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleQuickSelect("last30days")}
|
||||||
|
className="h-8 text-xs"
|
||||||
|
>
|
||||||
|
Last 30 Days
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleQuickSelect("thisMonth")}
|
||||||
|
className="h-8 text-xs"
|
||||||
|
>
|
||||||
|
This Month
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleQuickSelect("lastMonth")}
|
||||||
|
className="h-8 text-xs"
|
||||||
|
>
|
||||||
|
Last Month
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleQuickSelect("thisQuarter")}
|
||||||
|
className="h-8 text-xs"
|
||||||
|
>
|
||||||
|
This Quarter
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleQuickSelect("lastQuarter")}
|
||||||
|
className="h-8 text-xs"
|
||||||
|
>
|
||||||
|
Last Quarter
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleQuickSelect("thisYear")}
|
||||||
|
className="h-8 text-xs"
|
||||||
|
>
|
||||||
|
This Year
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="text-xs text-muted-foreground">Or type a custom period</div>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
placeholder="e.g., jan-may 24, 2021-2023, Q1-Q3 2024"
|
||||||
|
value={inputValue}
|
||||||
|
onChange={(event) => handleInputChange(event.target.value)}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
className="h-8 text-sm"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{inputValue && (
|
||||||
|
<div className="mt-2">
|
||||||
|
{preview.label ? (
|
||||||
|
<div className="flex items-center gap-2 text-xs">
|
||||||
|
<span className="text-muted-foreground">Recognized as:</span>
|
||||||
|
<span className="font-medium text-green-600 dark:text-green-400">
|
||||||
|
{preview.label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-xs text-amber-600 dark:text-amber-400">
|
||||||
|
Not recognized - try a different format
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showSuggestions && filteredSuggestions.length > 0 && (
|
||||||
|
<div className="absolute top-full left-0 right-0 z-50 mt-1 bg-white dark:bg-gray-800 border rounded-md shadow-lg max-h-32 overflow-y-auto">
|
||||||
|
{filteredSuggestions.slice(0, 6).map((suggestion) => (
|
||||||
|
<button
|
||||||
|
key={suggestion}
|
||||||
|
className="w-full text-left px-3 py-1.5 text-xs hover:bg-muted transition-colors"
|
||||||
|
onClick={() => handleSuggestionClick(suggestion)}
|
||||||
|
>
|
||||||
|
{suggestion}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{inputValue === "" && (
|
||||||
|
<div className="text-xs text-muted-foreground mt-2">
|
||||||
|
<div className="mb-1">Examples:</div>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{SUGGESTIONS.slice(0, 6).map((suggestion) => (
|
||||||
|
<button
|
||||||
|
key={suggestion}
|
||||||
|
className="px-2 py-0.5 bg-muted hover:bg-muted/80 rounded text-xs transition-colors"
|
||||||
|
onClick={() => handleSuggestionClick(suggestion)}
|
||||||
|
>
|
||||||
|
{suggestion}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PeriodSelectionPopover;
|
||||||
Reference in New Issue
Block a user