fix: resolve all TypeScript errors in web app

This commit is contained in:
Jason Woltje
2026-01-29 22:23:28 -06:00
parent abbf886483
commit 1e927751a9
23 changed files with 207 additions and 136 deletions

View File

@@ -5,7 +5,11 @@
import type { ReactNode } from "react";
import { Settings, X } from "lucide-react";
import { cn } from "@mosaic/ui/lib/utils";
// Simple classnames utility
function cn(...classes: (string | undefined | null | false)[]): string {
return classes.filter(Boolean).join(" ");
}
export interface BaseWidgetProps {
id: string;

View File

@@ -5,13 +5,17 @@
import { useCallback, useMemo } from "react";
import GridLayout from "react-grid-layout";
import type { Layout } from "react-grid-layout";
import type { Layout, LayoutItem } from "react-grid-layout";
import type { WidgetPlacement } from "@mosaic/shared";
import { cn } from "@mosaic/ui/lib/utils";
import { getWidgetByName } from "./WidgetRegistry";
import { BaseWidget } from "./BaseWidget";
import "react-grid-layout/css/styles.css";
// Simple classnames utility
function cn(...classes: (string | undefined | null | false)[]): string {
return classes.filter(Boolean).join(" ");
}
export interface WidgetGridProps {
layout: WidgetPlacement[];
onLayoutChange: (layout: WidgetPlacement[]) => void;
@@ -28,41 +32,51 @@ export function WidgetGrid({
className,
}: WidgetGridProps) {
// Convert WidgetPlacement to react-grid-layout Layout format
const gridLayout: Layout[] = useMemo(
const gridLayout: Layout = useMemo(
() =>
layout.map((item) => ({
i: item.i,
x: item.x,
y: item.y,
w: item.w,
h: item.h,
minW: item.minW,
maxW: item.maxW,
minH: item.minH,
maxH: item.maxH,
static: !isEditing || item.static,
isDraggable: isEditing && (item.isDraggable !== false),
isResizable: isEditing && (item.isResizable !== false),
})),
layout.map((item): LayoutItem => {
const layoutItem: LayoutItem = {
i: item.i,
x: item.x,
y: item.y,
w: item.w,
h: item.h,
static: !isEditing || (item.static ?? false),
isDraggable: isEditing && (item.isDraggable !== false),
isResizable: isEditing && (item.isResizable !== false),
};
if (item.minW !== undefined) layoutItem.minW = item.minW;
if (item.maxW !== undefined) layoutItem.maxW = item.maxW;
if (item.minH !== undefined) layoutItem.minH = item.minH;
if (item.maxH !== undefined) layoutItem.maxH = item.maxH;
return layoutItem;
}),
[layout, isEditing]
);
const handleLayoutChange = useCallback(
(newLayout: Layout[]) => {
const updatedLayout: WidgetPlacement[] = newLayout.map((item) => ({
i: item.i,
x: item.x,
y: item.y,
w: item.w,
h: item.h,
minW: item.minW,
maxW: item.maxW,
minH: item.minH,
maxH: item.maxH,
static: item.static,
isDraggable: item.isDraggable,
isResizable: item.isResizable,
}));
(newLayout: Layout) => {
const updatedLayout: WidgetPlacement[] = newLayout.map((item): WidgetPlacement => {
const placement: WidgetPlacement = {
i: item.i,
x: item.x,
y: item.y,
w: item.w,
h: item.h,
};
if (item.minW !== undefined) placement.minW = item.minW;
if (item.maxW !== undefined) placement.maxW = item.maxW;
if (item.minH !== undefined) placement.minH = item.minH;
if (item.maxH !== undefined) placement.maxH = item.maxH;
if (item.static !== undefined) placement.static = item.static;
if (item.isDraggable !== undefined) placement.isDraggable = item.isDraggable;
if (item.isResizable !== undefined) placement.isResizable = item.isResizable;
return placement;
});
onLayoutChange(updatedLayout);
},
[onLayoutChange]
@@ -97,24 +111,30 @@ export function WidgetGrid({
className="layout"
layout={gridLayout}
onLayoutChange={handleLayoutChange}
cols={12}
rowHeight={100}
width={1200}
isDraggable={isEditing}
isResizable={isEditing}
compactType="vertical"
preventCollision={false}
gridConfig={{
cols: 12,
rowHeight: 100,
}}
dragConfig={{
enabled: isEditing,
}}
resizeConfig={{
enabled: isEditing,
}}
data-testid="grid-layout"
>
{layout.map((item) => {
// Extract widget type from widget ID (format: "WidgetType-uuid")
const widgetType = item.i.split("-")[0];
const widgetType = item.i.split("-")[0]!;
const widgetDef = getWidgetByName(widgetType);
if (!widgetDef) {
return (
<div key={item.i} data-testid={`widget-${item.i}`}>
<BaseWidget id={item.i} title="Unknown Widget" error="Widget not found" />
<BaseWidget id={item.i} title="Unknown Widget" error="Widget not found">
<div />
</BaseWidget>
</div>
);
}
@@ -127,12 +147,9 @@ export function WidgetGrid({
id={item.i}
title={widgetDef.displayName}
description={widgetDef.description}
onEdit={isEditing ? undefined : undefined} // TODO: Implement edit
onRemove={
isEditing && onRemoveWidget
? () => handleRemoveWidget(item.i)
: undefined
}
{...(isEditing && onRemoveWidget && {
onRemove: () => handleRemoveWidget(item.i),
})}
>
<WidgetComponent id={item.i} />
</BaseWidget>

View File

@@ -3,7 +3,7 @@
* Following TDD principles
*/
import { describe, it, expect, vi } from "vitest";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { QuickCaptureWidget } from "../QuickCaptureWidget";

View File

@@ -17,21 +17,21 @@ describe("WidgetRegistry", () => {
it("should include TasksWidget in registry", () => {
expect(widgetRegistry.TasksWidget).toBeDefined();
expect(widgetRegistry.TasksWidget.component).toBe(TasksWidget);
expect(widgetRegistry.TasksWidget!.component).toBe(TasksWidget);
});
it("should include CalendarWidget in registry", () => {
expect(widgetRegistry.CalendarWidget).toBeDefined();
expect(widgetRegistry.CalendarWidget.component).toBe(CalendarWidget);
expect(widgetRegistry.CalendarWidget!.component).toBe(CalendarWidget);
});
it("should include QuickCaptureWidget in registry", () => {
expect(widgetRegistry.QuickCaptureWidget).toBeDefined();
expect(widgetRegistry.QuickCaptureWidget.component).toBe(QuickCaptureWidget);
expect(widgetRegistry.QuickCaptureWidget!.component).toBe(QuickCaptureWidget);
});
it("should have correct metadata for TasksWidget", () => {
const tasksWidget = widgetRegistry.TasksWidget;
const tasksWidget = widgetRegistry.TasksWidget!;
expect(tasksWidget.name).toBe("TasksWidget");
expect(tasksWidget.displayName).toBe("Tasks");
expect(tasksWidget.description).toBeDefined();
@@ -42,7 +42,7 @@ describe("WidgetRegistry", () => {
});
it("should have correct metadata for CalendarWidget", () => {
const calendarWidget = widgetRegistry.CalendarWidget;
const calendarWidget = widgetRegistry.CalendarWidget!;
expect(calendarWidget.name).toBe("CalendarWidget");
expect(calendarWidget.displayName).toBe("Calendar");
expect(calendarWidget.description).toBeDefined();
@@ -51,7 +51,7 @@ describe("WidgetRegistry", () => {
});
it("should have correct metadata for QuickCaptureWidget", () => {
const quickCaptureWidget = widgetRegistry.QuickCaptureWidget;
const quickCaptureWidget = widgetRegistry.QuickCaptureWidget!;
expect(quickCaptureWidget.name).toBe("QuickCaptureWidget");
expect(quickCaptureWidget.displayName).toBe("Quick Capture");
expect(quickCaptureWidget.description).toBeDefined();