application [wireframe-renderer-web] view page [pages/index] development

This commit is contained in:
NVWA Code Agent
2025-12-17 10:21:09 +00:00
parent 7304ae0c4b
commit 163295fc26
2 changed files with 158 additions and 9 deletions

View File

@@ -1,4 +1,5 @@
import { useState, useRef, useEffect } from "react";
import { useSearchParams } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
@@ -24,6 +25,7 @@ interface DrawingElement {
}
export default function CanvasPage() {
const [searchParams] = useSearchParams();
const canvasRef = useRef<HTMLCanvasElement>(null);
const [tool, setTool] = useState<Tool>("pen");
const [isDrawing, setIsDrawing] = useState(false);
@@ -198,6 +200,8 @@ export default function CanvasPage() {
const user = await auth.currentUser();
if (!user) return;
const projectIdFromUrl = searchParams.get("projectId");
// Load projects
const { data: userProjects } = await entities
.from("nvwa_project")
@@ -206,8 +210,14 @@ export default function CanvasPage() {
if (userProjects && userProjects.length > 0) {
setProjects(userProjects);
// Use first project or create new one
const project = userProjects[0];
// Use specified project or first one
let project;
if (projectIdFromUrl) {
project = userProjects.find(p => p.id === parseInt(projectIdFromUrl));
}
if (!project) {
project = userProjects[0];
}
setCurrentProject(project);
// Load latest wireframe for this project
@@ -230,7 +240,7 @@ export default function CanvasPage() {
}
}
} else {
// Create default project
// Create default project if no projects exist
const { data: newProject } = await entities
.from("nvwa_project")
.insert({

View File

@@ -1,11 +1,150 @@
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { Plus, Folder } from "lucide-react";
import { entities, auth } from "@/lib/nvwa";
export default function HomePage() {
const [projects, setProjects] = useState([]);
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const [newProjectName, setNewProjectName] = useState("");
const [newProjectDescription, setNewProjectDescription] = useState("");
const navigate = useNavigate();
useEffect(() => {
loadProjects();
}, []);
const loadProjects = async () => {
try {
const user = await auth.currentUser();
if (!user) return;
const { data } = await entities
.from("nvwa_project")
.select("*")
.eq("user_id", user.id)
.order("updated_at", { ascending: false });
setProjects(data || []);
} catch (error) {
console.error("Failed to load projects:", error);
}
};
const createProject = async () => {
try {
const user = await auth.currentUser();
if (!user || !newProjectName.trim()) return;
const { data } = await entities
.from("nvwa_project")
.insert({
name: newProjectName,
description: newProjectDescription,
user_id: user.id
})
.select();
if (data && data.length > 0) {
setProjects(prev => [data[0], ...prev]);
setNewProjectName("");
setNewProjectDescription("");
setIsCreateDialogOpen(false);
}
} catch (error) {
console.error("Failed to create project:", error);
}
};
const openProject = (projectId) => {
navigate(`/canvas?projectId=${projectId}`);
};
return (
<div className="flex min-h-screen items-center justify-center bg-background p-4">
<div className="text-center">
<h1 className="text-2xl font-bold mb-4"></h1>
<p className="text-muted-foreground"></p>
<div className="container mx-auto p-4">
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold"></h1>
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
<DialogTrigger asChild>
<Button>
<Plus className="w-4 h-4 mr-2" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label htmlFor="name"></Label>
<Input
id="name"
value={newProjectName}
onChange={(e) => setNewProjectName(e.target.value)}
placeholder="输入项目名称"
/>
</div>
<div>
<Label htmlFor="desc"></Label>
<Textarea
id="desc"
value={newProjectDescription}
onChange={(e) => setNewProjectDescription(e.target.value)}
placeholder="输入项目描述(可选)"
rows={3}
/>
</div>
<Button onClick={createProject} disabled={!newProjectName.trim()}>
</Button>
</div>
</DialogContent>
</Dialog>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{projects.map((project) => (
<Card
key={project.id}
className="cursor-pointer hover:shadow-md transition-shadow"
onClick={() => openProject(project.id)}
>
<CardHeader>
<CardTitle className="flex items-center">
<Folder className="w-5 h-5 mr-2" />
{project.name}
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground mb-2">
{project.description || "无描述"}
</p>
<p className="text-xs text-muted-foreground">
{new Date(project.created_at).toLocaleDateString()}
</p>
</CardContent>
</Card>
))}
</div>
{projects.length === 0 && (
<div className="text-center py-12">
<Folder className="w-12 h-12 mx-auto mb-4 text-muted-foreground" />
<h3 className="text-lg font-medium mb-2"></h3>
<p className="text-muted-foreground mb-4"></p>
<Button onClick={() => setIsCreateDialogOpen(true)}>
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
)}
</div>
);
}