clean ups

This commit is contained in:
Dhruwang
2026-01-29 10:46:32 +04:00
parent ea3b4b9413
commit ef56e97c95
5 changed files with 173 additions and 66 deletions

View File

@@ -61,6 +61,70 @@ export const createChartAction = authenticatedActionClient.schema(ZCreateChartAc
)
);
const ZUpdateChartAction = z.object({
environmentId: ZId,
chartId: ZId,
name: z.string().min(1).optional(),
type: z
.enum(["area", "bar", "line", "pie", "big_number", "big_number_total", "table", "funnel", "map"])
.optional(),
query: z.record(z.any()).optional(),
config: z.record(z.any()).optional(),
});
export const updateChartAction = authenticatedActionClient.schema(ZUpdateChartAction).action(
withAuditLogging(
"updated",
"chart",
async ({ ctx, parsedInput }: { ctx: any; parsedInput: z.infer<typeof ZUpdateChartAction> }) => {
const organizationId = await getOrganizationIdFromEnvironmentId(parsedInput.environmentId);
const projectId = await getProjectIdFromEnvironmentId(parsedInput.environmentId);
await checkAuthorizationUpdated({
userId: ctx.user.id,
organizationId,
access: [
{
type: "organization",
roles: ["owner", "manager"],
},
{
type: "projectTeam",
minPermission: "readWrite",
projectId,
},
],
});
// Verify chart belongs to the project
const chart = await prisma.chart.findFirst({
where: { id: parsedInput.chartId, projectId },
});
if (!chart) {
throw new Error("Chart not found");
}
const updateData: any = {};
if (parsedInput.name !== undefined) updateData.name = parsedInput.name;
if (parsedInput.type !== undefined) updateData.type = parsedInput.type as ChartType;
if (parsedInput.query !== undefined) updateData.query = parsedInput.query;
if (parsedInput.config !== undefined) updateData.config = parsedInput.config;
const updatedChart = await prisma.chart.update({
where: { id: parsedInput.chartId },
data: updateData,
});
ctx.auditLoggingCtx.organizationId = organizationId;
ctx.auditLoggingCtx.projectId = projectId;
ctx.auditLoggingCtx.oldObject = chart;
ctx.auditLoggingCtx.newObject = updatedChart;
return updatedChart;
}
)
);
const ZAddChartToDashboardAction = z.object({
environmentId: ZId,
chartId: ZId,

View File

@@ -11,6 +11,7 @@ import {
executeQueryAction,
getChartAction,
getDashboardsAction,
updateChartAction,
} from "../../actions";
import { mapChartType, mapDatabaseChartTypeToApi } from "../lib/chart-utils";
import { AIQuerySection } from "./AIQuerySection";
@@ -42,6 +43,7 @@ export function ChartBuilderClient({ environmentId, chartId }: ChartBuilderClien
const [configuredChartType, setConfiguredChartType] = useState<string | null>(null);
const [showAdvancedBuilder, setShowAdvancedBuilder] = useState(false);
const [isLoadingChart, setIsLoadingChart] = useState(false);
const [currentChartId, setCurrentChartId] = useState<string | undefined>(chartId);
useEffect(() => {
if (isAddToDashboardDialogOpen) {
@@ -86,6 +88,7 @@ export function ChartBuilderClient({ environmentId, chartId }: ChartBuilderClien
setChartData(chartData);
setConfiguredChartType(mapDatabaseChartTypeToApi(chart.type));
setCurrentChartId(chart.id);
} else {
toast.error("No data returned for chart");
}
@@ -114,22 +117,44 @@ export function ChartBuilderClient({ environmentId, chartId }: ChartBuilderClien
setIsSaving(true);
try {
const result = await createChartAction({
environmentId,
name: chartName,
type: mapChartType(chartData.chartType),
query: chartData.query,
config: {},
});
// If we have a currentChartId, update the existing chart; otherwise create a new one
if (currentChartId) {
const result = await updateChartAction({
environmentId,
chartId: currentChartId,
name: chartName.trim(),
type: mapChartType(chartData.chartType),
query: chartData.query,
config: {},
});
if (!result?.data) {
toast.error(result?.serverError || "Failed to save chart");
return;
if (!result?.data) {
toast.error(result?.serverError || "Failed to update chart");
return;
}
toast.success("Chart updated successfully!");
setIsSaveDialogOpen(false);
router.push(`/environments/${environmentId}/analysis/charts`);
} else {
const result = await createChartAction({
environmentId,
name: chartName.trim(),
type: mapChartType(chartData.chartType),
query: chartData.query,
config: {},
});
if (!result?.data) {
toast.error(result?.serverError || "Failed to save chart");
return;
}
setCurrentChartId(result.data.id);
toast.success("Chart saved successfully!");
setIsSaveDialogOpen(false);
router.push(`/environments/${environmentId}/analysis/charts`);
}
toast.success("Chart saved successfully!");
setIsSaveDialogOpen(false);
router.push(`/environments/${environmentId}/analysis/charts`);
} catch (error: any) {
toast.error(error.message || "Failed to save chart");
} finally {
@@ -145,22 +170,38 @@ export function ChartBuilderClient({ environmentId, chartId }: ChartBuilderClien
setIsSaving(true);
try {
const chartResult = await createChartAction({
environmentId,
name: chartName || `Chart ${new Date().toLocaleString()}`,
type: mapChartType(chartData.chartType),
query: chartData.query,
config: {},
});
let chartIdToUse = currentChartId;
if (!chartResult?.data) {
toast.error(chartResult?.serverError || "Failed to save chart");
return;
// If we don't have a chartId (creating new chart), create it first
if (!chartIdToUse) {
if (!chartName.trim()) {
toast.error("Please enter a chart name");
setIsSaving(false);
return;
}
const chartResult = await createChartAction({
environmentId,
name: chartName.trim(),
type: mapChartType(chartData.chartType),
query: chartData.query,
config: {},
});
if (!chartResult?.data) {
toast.error(chartResult?.serverError || "Failed to save chart");
setIsSaving(false);
return;
}
chartIdToUse = chartResult.data.id;
setCurrentChartId(chartIdToUse);
}
// Add the chart (existing or newly created) to the dashboard
const widgetResult = await addChartToDashboardAction({
environmentId,
chartId: chartResult.data.id,
chartId: chartIdToUse,
dashboardId: selectedDashboardId,
});
@@ -212,6 +253,37 @@ export function ChartBuilderClient({ environmentId, chartId }: ChartBuilderClien
onSave={() => setIsSaveDialogOpen(true)}
onAddToDashboard={() => setIsAddToDashboardDialogOpen(true)}
/>
{/* Dialogs */}
<SaveChartDialog
open={isSaveDialogOpen}
onOpenChange={setIsSaveDialogOpen}
chartName={chartName}
onChartNameChange={setChartName}
onSave={handleSaveChart}
isSaving={isSaving}
/>
<AddToDashboardDialog
open={isAddToDashboardDialogOpen}
onOpenChange={setIsAddToDashboardDialogOpen}
chartName={chartName}
onChartNameChange={setChartName}
dashboards={dashboards}
selectedDashboardId={selectedDashboardId}
onDashboardSelect={setSelectedDashboardId}
onAdd={handleAddToDashboard}
isSaving={isSaving}
/>
<ConfigureChartDialog
open={isConfigureDialogOpen}
onOpenChange={setIsConfigureDialogOpen}
currentChartType={chartData?.chartType || "bar"}
configuredChartType={configuredChartType}
onChartTypeSelect={setConfiguredChartType}
onReset={() => setConfiguredChartType(null)}
/>
</div>
);
}

View File

@@ -25,9 +25,9 @@ export function ChartsListClient({ charts, dashboards, environmentId }: ChartsLi
return (
<div className="flex h-full flex-col">
{/* Header / Actions */}
<div className="flex flex-col gap-4 border-b border-gray-200 bg-white px-6 py-4">
<div className="flex flex-col gap-4 border-gray-200 px-6 py-4">
<div className="flex items-center justify-between">
<h1 className="text-xl font-semibold text-gray-900">Charts</h1>
<h1 className="text-xl font-semibold text-gray-900"></h1>
<div className="flex items-center gap-2">
<Link href="chart-builder">
<Button size="sm">
@@ -38,22 +38,7 @@ export function ChartsListClient({ charts, dashboards, environmentId }: ChartsLi
</div>
</div>
<div className="flex items-center gap-4">
<div className="relative">
<SearchIcon className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400" />
<Input placeholder="Type a value" className="w-[300px] pl-9" />
</div>
{/* Filter Dropdowns */}
<div className="no-scrollbar flex items-center gap-2 overflow-x-auto">
{["Type", "Dataset", "Owner", "Dashboard", "Favorite", "Certified", "Modified by"].map(
(filter) => (
<Button key={filter} variant="outline" className="whitespace-nowrap text-gray-500" size="sm">
{filter} <span className="ml-2 text-xs"></span>
</Button>
)
)}
</div>
</div>
<div className="flex items-center gap-4"></div>
</div>
{/* Table Content */}

View File

@@ -1,5 +1,6 @@
"use client";
import { Button } from "@/modules/ui/components/button";
import {
Dialog,
DialogBody,
@@ -9,7 +10,6 @@ import {
DialogHeader,
DialogTitle,
} from "@/modules/ui/components/dialog";
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
interface CreateDashboardDialogProps {
@@ -35,7 +35,7 @@ export function CreateDashboardDialog({
}: CreateDashboardDialogProps) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>Create Dashboard</DialogTitle>
<DialogDescription>Enter a name for your dashboard to create it.</DialogDescription>

View File

@@ -15,15 +15,18 @@ import {
} from "@/modules/ui/components/dropdown-menu";
import { Input } from "@/modules/ui/components/input";
import { createDashboardAction } from "../../actions";
import { CreateDashboardDialog } from "./CreateDashboardDialog";
import { TDashboard } from "../../types/analysis";
import { CreateDashboardDialog } from "./CreateDashboardDialog";
interface DashboardsListClientProps {
dashboards: TDashboard[];
environmentId: string;
}
export function DashboardsListClient({ dashboards: initialDashboards, environmentId }: DashboardsListClientProps) {
export function DashboardsListClient({
dashboards: initialDashboards,
environmentId,
}: DashboardsListClientProps) {
const router = useRouter();
const [searchQuery, setSearchQuery] = useState("");
const [dashboards] = useState(initialDashboards);
@@ -75,25 +78,8 @@ export function DashboardsListClient({ dashboards: initialDashboards, environmen
return (
<div className="flex h-full flex-col">
{/* Header / Actions */}
<div className="flex items-center justify-between border-b border-gray-200 bg-white px-6 py-4">
<div className="flex items-center gap-4">
<div className="relative">
<SearchIcon className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400" />
<Input
placeholder="Search dashboards"
className="w-[300px] pl-9"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
{/* Filter Dropdowns - Visual Only for MVP port */}
<Button variant="outline" className="text-gray-500">
Owner <span className="ml-2 text-xs"></span>
</Button>
<Button variant="outline" className="text-gray-500">
Status <span className="ml-2 text-xs"></span>
</Button>
</div>
<div className="flex items-center justify-between border-gray-200 px-6 py-4">
<div className="flex items-center gap-4"></div>
<Button onClick={handleCreateDashboard}>
<PlusIcon className="mr-2 h-4 w-4" />
Dashboard