Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 70 additions & 2 deletions components/FieldSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,74 @@ import {
import { Badge } from "./ui/badge";
import { TbNumber123 } from "react-icons/tb";
import { PiTextAaFill } from "react-icons/pi";
import { Columns3, Database, Rows3, SquareSigma } from "lucide-react";
import { Clock, Columns3, Database, Rows3, SquareSigma } from "lucide-react";
import { useFileStore } from "@/stores/useFileStore";
import { usePivotStore } from "@/stores/usePivotStore";
import FilterDialog from "./FilterDialog";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "./ui/dialog";

function DateOptionsDialog({ table, field }: { table: string; field: string }) {
const { addRow, addColumn } = usePivotStore();

const options = [
{ label: "Year", extract: "YEAR" },
{ label: "Month", extract: "MONTH" },
{ label: "Quarter", extract: "QUARTER" },
] as const;

return (
<Dialog>
<DialogTrigger>
<Clock size={20} className="cursor-pointer hover:text-black" />
</DialogTrigger>
<DialogContent className="bg-gray-700 max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Date parsing</DialogTitle>
<DialogDescription className="text-white">
Date parse {field} from {table}
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-4 p-4">
<h4 className="font-medium leading-none">Date Field Options</h4>
<div className="flex flex-col gap-2">
{options.map((opt) => (
<div
key={opt.extract}
className="flex justify-between items-center"
>
<span>{opt.label}</span>
<div className="flex gap-1">
<Rows3
size={20}
className="cursor-pointer hover:text-black"
onClick={() => addRow(table, field, opt.extract)}
/>
<Columns3
size={20}
className="cursor-pointer hover:text-black"
onClick={() => addColumn(table, field, opt.extract)}
/>
<FilterDialog
table={table}
field={field}
dateExtract={opt.extract}
/>
</div>
</div>
))}
</div>
</div>
</DialogContent>
</Dialog>
);
}

export default function FieldSelection() {
const { queryFields, setQueryFields, isLoadingFields } = useTableStore();
Expand Down Expand Up @@ -74,7 +138,7 @@ export default function FieldSelection() {
handleTypeChange(parentKey.name, index)
}
className="cursor-pointer hover:text-black"
title="Current format: Text. Click to change to number."
title="Current format: Text. Click to change to number or date."
/>
<Rows3
size={20}
Expand All @@ -101,6 +165,10 @@ export default function FieldSelection() {
title="Current format: Number. Click to change to text."
className="cursor-pointer hover:text-black"
/>
<DateOptionsDialog
table={parentKey.name}
field={item.name}
/>
<SquareSigma
size={20}
className="cursor-pointer hover:text-black"
Expand Down
86 changes: 55 additions & 31 deletions components/FilterDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
ChevronsLeft,
ChevronsRight,
} from "lucide-react";
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { Button } from "./ui/button";
import {
Table,
Expand All @@ -27,7 +27,6 @@ import {
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { usePivotStore } from "@/stores/usePivotStore";
import { Table as Arrow } from "apache-arrow";
import {
Collapsible,
CollapsibleContent,
Expand All @@ -37,13 +36,17 @@ import { ChevronDown, ChevronUp } from "lucide-react";
import { Separator } from "./ui/separator";
import { useToast } from "@/hooks/use-toast";

type FilterDialogProps = {
table: string;
field: string;
dateExtract?: "YEAR" | "MONTH" | "QUARTER";
};

export default function FilterDialog({
table,
field,
}: {
table: string;
field: string;
}) {
dateExtract,
}: FilterDialogProps) {
const { toast } = useToast();
const { db, runQuery } = useDuckDBStore();
const { filters, addFilter } = usePivotStore();
Expand All @@ -60,36 +63,55 @@ export default function FilterDialog({
const itemsPerPage = 10;

const fetchData = async () => {
if (db) {
setLoading(true);
const result: Arrow = await runQuery(
db,
`
SELECT DISTINCT "${field}"
FROM '${table}'
ORDER BY "${field}" ASC`
);
if (!db) return;
setLoading(true);

try {
let query;
// Extract the original field name if it's already a date-extracted field
const originalField = field.match(/^(YEAR|MONTH|QUARTER)\((.*?)\)$/);
const actualField = originalField ? originalField[2] : field;
const actualExtract = originalField ? originalField[1] : dateExtract;

const cleanedData = result.toArray().map((row) => {
if (actualExtract) {
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`;
} else {
query = `SELECT DISTINCT REPLACE("${actualField}", '"', '') as value FROM '${table}' WHERE "${actualField}" IS NOT NULL ORDER BY value`;
}
const result = await runQuery(db, query);

const values = result
.toArray()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const cleanedRow: any = {};
for (const [key, value] of Object.entries(row)) {
cleanedRow[key] =
typeof value === "string" ? value.replace(/"/g, "") : value;
}
return cleanedRow;
.map((row: { value: { toString: () => any } }) => row.value.toString());
setValues(values);
} catch (error) {
console.error("Error fetching filter options:", error);
toast({
title: "Error",
description:
"Failed to fetch filter values. Please make sure this is a proper date or text field.",
variant: "destructive",
});

setValues(
cleanedData.map(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(obj: any) => obj[field]?.toString() ?? "(Null)"
)
);
} finally {
setLoading(false);
}
};

// Remove the automatic fetching from useEffect since we now have a manual fetch button
useEffect(() => {
// Reset selected values when dialog opens
if (open) {
// Find the filter checking both regular and date-extracted field names
const existingFilter = filters.find(
(f) =>
f.table === table &&
(f.field === field || f.field === `${dateExtract}(${field})`)
);
setSelectedValues(existingFilter?.values || []);
}
}, [open, table, field, dateExtract, filters]);

const filteredValues = values.filter((value) =>
value.toString().toLowerCase().includes(searchQuery.toLowerCase())
);
Expand All @@ -108,7 +130,7 @@ export default function FilterDialog({
};

const handleSubmit = () => {
addFilter(table, field, selectedValues);
addFilter(table, field, selectedValues, dateExtract);
toast({
title: "Filter Applied",
description: `Successfully applied filter for ${field}`,
Expand Down Expand Up @@ -138,7 +160,9 @@ export default function FilterDialog({
<DialogHeader>
<DialogTitle>Add filter</DialogTitle>
<DialogDescription className="text-white">
Add filter for {field}
{dateExtract
? `Filter by ${dateExtract.toLowerCase()}s from "${field}" in "${table}"`
: `Add filter for "${field}" from "${table}"`}
</DialogDescription>
</DialogHeader>
<div className="flex flex-col space-y-4">
Expand Down
Loading