Implement initial application structure and UI.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 9111ef36-26c8-4085-84ca-a35dc1fec1b5
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7083d608-d6d3-4a6a-9a27-6286c5109627/54918520-d42c-44cd-b4f4-9e5a42eb5be1.jpg
This commit is contained in:
alphaeusmote
2025-04-09 21:34:26 +00:00
parent e1340a29cb
commit cba930e4d3
99 changed files with 21903 additions and 0 deletions
@@ -0,0 +1,63 @@
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useOrganization } from "@/context/organization-context";
import { ChevronDown } from "lucide-react";
export function OrganizationSelector() {
const { organizations, currentOrganization, setCurrentOrganization, isLoading } = useOrganization();
if (isLoading) {
return (
<div className="w-full flex items-center justify-between px-3 py-2 text-sm font-medium rounded-md bg-secondary text-secondary-foreground opacity-70 animate-pulse">
<div className="flex items-center">
<span className="w-2 h-2 bg-muted rounded-full mr-2"></span>
<span>Loading...</span>
</div>
</div>
);
}
if (!currentOrganization || organizations.length === 0) {
return (
<div className="w-full flex items-center justify-between px-3 py-2 text-sm font-medium rounded-md bg-secondary text-secondary-foreground">
<div className="flex items-center">
<span className="w-2 h-2 bg-warning rounded-full mr-2"></span>
<span>No Organization</span>
</div>
</div>
);
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="w-full flex items-center justify-between px-3 py-2 text-sm font-medium rounded-md hover:bg-accent">
<div className="flex items-center">
<span className="w-2 h-2 bg-success rounded-full mr-2"></span>
<span>{currentOrganization.name}</span>
</div>
<ChevronDown className="h-4 w-4 opacity-50" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-64">
{organizations.map((org) => (
<DropdownMenuItem
key={org.id}
onClick={() => setCurrentOrganization(org)}
className={org.id === currentOrganization.id ? "bg-muted" : ""}
>
<div className="flex items-center">
<span className={`w-2 h-2 rounded-full mr-2 ${org.isActive ? "bg-success" : "bg-warning"}`}></span>
<span>{org.name}</span>
</div>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}
+110
View File
@@ -0,0 +1,110 @@
import { Button } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
interface PaginationProps {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
itemLabel?: string;
totalItems?: number;
itemsPerPage?: number;
}
export function Pagination({
currentPage,
totalPages,
onPageChange,
itemLabel = "items",
totalItems,
itemsPerPage = 10,
}: PaginationProps) {
// Generate page numbers to display
const getPageNumbers = () => {
const pages = [];
// Always include first page
pages.push(1);
// Add ellipsis if needed
if (currentPage > 3) {
pages.push("ellipsis1");
}
// Add pages around current page
for (let i = Math.max(2, currentPage - 1); i <= Math.min(totalPages - 1, currentPage + 1); i++) {
if (i > 1 && i < totalPages) {
pages.push(i);
}
}
// Add ellipsis if needed
if (currentPage < totalPages - 2) {
pages.push("ellipsis2");
}
// Always include last page if more than 1 page
if (totalPages > 1) {
pages.push(totalPages);
}
return pages;
};
const pageNumbers = getPageNumbers();
const startItem = (currentPage - 1) * itemsPerPage + 1;
const endItem = Math.min(startItem + itemsPerPage - 1, totalItems || 0);
return (
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
{totalItems !== undefined && (
<div className="text-sm text-muted-foreground">
Showing {startItem}-{endItem} of {totalItems} {itemLabel}
</div>
)}
<div className="flex space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage === 1}
>
<ChevronLeft className="h-4 w-4 mr-1" />
Previous
</Button>
{pageNumbers.map((page, index) => {
if (page === "ellipsis1" || page === "ellipsis2") {
return (
<Button key={page} variant="outline" size="sm" disabled>
...
</Button>
);
}
return (
<Button
key={page}
variant={currentPage === page ? "default" : "outline"}
size="sm"
onClick={() => onPageChange(page as number)}
>
{page}
</Button>
);
})}
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}
>
Next
<ChevronRight className="h-4 w-4 ml-1" />
</Button>
</div>
</div>
);
}
@@ -0,0 +1,24 @@
import { Button } from "@/components/ui/button";
import { useTheme } from "@/hooks/use-theme";
import { Moon, Sun } from "lucide-react";
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
const toggleTheme = () => {
setTheme(theme === "dark" ? "light" : "dark");
};
return (
<Button
variant="outline"
size="icon"
onClick={toggleTheme}
title={theme === "dark" ? "Switch to light mode" : "Switch to dark mode"}
>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
);
}