Skip to content

Commit da6c008

Browse files
authored
Merge pull request #27 from danilo-css/master
Add date handling feature
2 parents 92300b3 + 0c86e36 commit da6c008

File tree

4 files changed

+274
-98
lines changed

4 files changed

+274
-98
lines changed

components/FieldSelection.tsx

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,74 @@ import {
1111
import { Badge } from "./ui/badge";
1212
import { TbNumber123 } from "react-icons/tb";
1313
import { PiTextAaFill } from "react-icons/pi";
14-
import { Columns3, Database, Rows3, SquareSigma } from "lucide-react";
14+
import { Clock, Columns3, Database, Rows3, SquareSigma } from "lucide-react";
1515
import { useFileStore } from "@/stores/useFileStore";
1616
import { usePivotStore } from "@/stores/usePivotStore";
1717
import FilterDialog from "./FilterDialog";
18+
import {
19+
Dialog,
20+
DialogContent,
21+
DialogDescription,
22+
DialogHeader,
23+
DialogTitle,
24+
DialogTrigger,
25+
} from "./ui/dialog";
26+
27+
function DateOptionsDialog({ table, field }: { table: string; field: string }) {
28+
const { addRow, addColumn } = usePivotStore();
29+
30+
const options = [
31+
{ label: "Year", extract: "YEAR" },
32+
{ label: "Month", extract: "MONTH" },
33+
{ label: "Quarter", extract: "QUARTER" },
34+
] as const;
35+
36+
return (
37+
<Dialog>
38+
<DialogTrigger>
39+
<Clock size={20} className="cursor-pointer hover:text-black" />
40+
</DialogTrigger>
41+
<DialogContent className="bg-gray-700 max-h-[90vh] overflow-y-auto">
42+
<DialogHeader>
43+
<DialogTitle>Date parsing</DialogTitle>
44+
<DialogDescription className="text-white">
45+
Date parse {field} from {table}
46+
</DialogDescription>
47+
</DialogHeader>
48+
<div className="flex flex-col gap-4 p-4">
49+
<h4 className="font-medium leading-none">Date Field Options</h4>
50+
<div className="flex flex-col gap-2">
51+
{options.map((opt) => (
52+
<div
53+
key={opt.extract}
54+
className="flex justify-between items-center"
55+
>
56+
<span>{opt.label}</span>
57+
<div className="flex gap-1">
58+
<Rows3
59+
size={20}
60+
className="cursor-pointer hover:text-black"
61+
onClick={() => addRow(table, field, opt.extract)}
62+
/>
63+
<Columns3
64+
size={20}
65+
className="cursor-pointer hover:text-black"
66+
onClick={() => addColumn(table, field, opt.extract)}
67+
/>
68+
<FilterDialog
69+
table={table}
70+
field={field}
71+
dateExtract={opt.extract}
72+
/>
73+
</div>
74+
</div>
75+
))}
76+
</div>
77+
</div>
78+
</DialogContent>
79+
</Dialog>
80+
);
81+
}
1882

1983
export default function FieldSelection() {
2084
const { queryFields, setQueryFields, isLoadingFields } = useTableStore();
@@ -74,7 +138,7 @@ export default function FieldSelection() {
74138
handleTypeChange(parentKey.name, index)
75139
}
76140
className="cursor-pointer hover:text-black"
77-
title="Current format: Text. Click to change to number."
141+
title="Current format: Text. Click to change to number or date."
78142
/>
79143
<Rows3
80144
size={20}
@@ -101,6 +165,10 @@ export default function FieldSelection() {
101165
title="Current format: Number. Click to change to text."
102166
className="cursor-pointer hover:text-black"
103167
/>
168+
<DateOptionsDialog
169+
table={parentKey.name}
170+
field={item.name}
171+
/>
104172
<SquareSigma
105173
size={20}
106174
className="cursor-pointer hover:text-black"

components/FilterDialog.tsx

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
ChevronsLeft,
1616
ChevronsRight,
1717
} from "lucide-react";
18-
import React, { useState } from "react";
18+
import React, { useState, useEffect } from "react";
1919
import { Button } from "./ui/button";
2020
import {
2121
Table,
@@ -27,7 +27,6 @@ import {
2727
import { Input } from "@/components/ui/input";
2828
import { Checkbox } from "@/components/ui/checkbox";
2929
import { usePivotStore } from "@/stores/usePivotStore";
30-
import { Table as Arrow } from "apache-arrow";
3130
import {
3231
Collapsible,
3332
CollapsibleContent,
@@ -37,13 +36,17 @@ import { ChevronDown, ChevronUp } from "lucide-react";
3736
import { Separator } from "./ui/separator";
3837
import { useToast } from "@/hooks/use-toast";
3938

39+
type FilterDialogProps = {
40+
table: string;
41+
field: string;
42+
dateExtract?: "YEAR" | "MONTH" | "QUARTER";
43+
};
44+
4045
export default function FilterDialog({
4146
table,
4247
field,
43-
}: {
44-
table: string;
45-
field: string;
46-
}) {
48+
dateExtract,
49+
}: FilterDialogProps) {
4750
const { toast } = useToast();
4851
const { db, runQuery } = useDuckDBStore();
4952
const { filters, addFilter } = usePivotStore();
@@ -60,36 +63,55 @@ export default function FilterDialog({
6063
const itemsPerPage = 10;
6164

6265
const fetchData = async () => {
63-
if (db) {
64-
setLoading(true);
65-
const result: Arrow = await runQuery(
66-
db,
67-
`
68-
SELECT DISTINCT "${field}"
69-
FROM '${table}'
70-
ORDER BY "${field}" ASC`
71-
);
66+
if (!db) return;
67+
setLoading(true);
68+
69+
try {
70+
let query;
71+
// Extract the original field name if it's already a date-extracted field
72+
const originalField = field.match(/^(YEAR|MONTH|QUARTER)\((.*?)\)$/);
73+
const actualField = originalField ? originalField[2] : field;
74+
const actualExtract = originalField ? originalField[1] : dateExtract;
7275

73-
const cleanedData = result.toArray().map((row) => {
76+
if (actualExtract) {
77+
query = `SELECT DISTINCT REPLACE(CAST(EXTRACT(${actualExtract} FROM CAST("${actualField}" AS DATE)) AS VARCHAR), '"', '') as value FROM '${table}' WHERE "${actualField}" IS NOT NULL ORDER BY value`;
78+
} else {
79+
query = `SELECT DISTINCT REPLACE("${actualField}", '"', '') as value FROM '${table}' WHERE "${actualField}" IS NOT NULL ORDER BY value`;
80+
}
81+
const result = await runQuery(db, query);
82+
83+
const values = result
84+
.toArray()
7485
// eslint-disable-next-line @typescript-eslint/no-explicit-any
75-
const cleanedRow: any = {};
76-
for (const [key, value] of Object.entries(row)) {
77-
cleanedRow[key] =
78-
typeof value === "string" ? value.replace(/"/g, "") : value;
79-
}
80-
return cleanedRow;
86+
.map((row: { value: { toString: () => any } }) => row.value.toString());
87+
setValues(values);
88+
} catch (error) {
89+
console.error("Error fetching filter options:", error);
90+
toast({
91+
title: "Error",
92+
description:
93+
"Failed to fetch filter values. Please make sure this is a proper date or text field.",
94+
variant: "destructive",
8195
});
82-
83-
setValues(
84-
cleanedData.map(
85-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
86-
(obj: any) => obj[field]?.toString() ?? "(Null)"
87-
)
88-
);
96+
} finally {
8997
setLoading(false);
9098
}
9199
};
92100

101+
// Remove the automatic fetching from useEffect since we now have a manual fetch button
102+
useEffect(() => {
103+
// Reset selected values when dialog opens
104+
if (open) {
105+
// Find the filter checking both regular and date-extracted field names
106+
const existingFilter = filters.find(
107+
(f) =>
108+
f.table === table &&
109+
(f.field === field || f.field === `${dateExtract}(${field})`)
110+
);
111+
setSelectedValues(existingFilter?.values || []);
112+
}
113+
}, [open, table, field, dateExtract, filters]);
114+
93115
const filteredValues = values.filter((value) =>
94116
value.toString().toLowerCase().includes(searchQuery.toLowerCase())
95117
);
@@ -108,7 +130,7 @@ export default function FilterDialog({
108130
};
109131

110132
const handleSubmit = () => {
111-
addFilter(table, field, selectedValues);
133+
addFilter(table, field, selectedValues, dateExtract);
112134
toast({
113135
title: "Filter Applied",
114136
description: `Successfully applied filter for ${field}`,
@@ -138,7 +160,9 @@ export default function FilterDialog({
138160
<DialogHeader>
139161
<DialogTitle>Add filter</DialogTitle>
140162
<DialogDescription className="text-white">
141-
Add filter for {field}
163+
{dateExtract
164+
? `Filter by ${dateExtract.toLowerCase()}s from "${field}" in "${table}"`
165+
: `Add filter for "${field}" from "${table}"`}
142166
</DialogDescription>
143167
</DialogHeader>
144168
<div className="flex flex-col space-y-4">

0 commit comments

Comments
 (0)