🚀 Initial commit: Versão atual do TrackSteel APP

This commit is contained in:
2026-03-18 21:17:53 +00:00
commit bde410c9ad
633 changed files with 108150 additions and 0 deletions
@@ -0,0 +1,69 @@
import React from 'react';
import { ChevronLeft, Menu, X } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
import { useMobileResponsive } from '@/hooks/useMobileResponsive';
interface MobileNavigationProps {
title: string;
onBack?: () => void;
actions?: React.ReactNode;
children?: React.ReactNode;
}
export function MobileNavigation({ title, onBack, actions, children }: MobileNavigationProps) {
const { isMobile } = useMobileResponsive();
if (!isMobile) {
return (
<div className="flex items-center justify-between p-4 border-b border-border">
<h1 className="text-xl font-semibold text-foreground">{title}</h1>
{actions && <div className="flex items-center gap-2">{actions}</div>}
</div>
);
}
return (
<div className="sticky top-0 z-40 bg-background/95 backdrop-blur-sm border-b border-border">
<div className="flex items-center justify-between p-4">
<div className="flex items-center gap-3">
{onBack && (
<Button
variant="ghost"
size="icon"
onClick={onBack}
className="touch-target"
>
<ChevronLeft className="h-5 w-5" />
</Button>
)}
<h1 className="text-lg font-semibold text-foreground truncate">{title}</h1>
</div>
<div className="flex items-center gap-2">
{actions && (
<Sheet>
<SheetTrigger asChild>
<Button variant="ghost" size="icon" className="touch-target">
<Menu className="h-5 w-5" />
</Button>
</SheetTrigger>
<SheetContent side="right" className="w-80">
<div className="flex flex-col gap-4 mt-8">
{actions}
</div>
</SheetContent>
</Sheet>
)}
</div>
</div>
{children && (
<div className="px-4 pb-4 border-t border-border">
{children}
</div>
)}
</div>
);
}
@@ -0,0 +1,90 @@
import React from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { useMobileResponsive, getResponsiveClasses } from '@/hooks/useMobileResponsive';
interface ResponsiveFormProps {
title?: string;
children: React.ReactNode;
actions?: React.ReactNode;
onSubmit?: (e: React.FormEvent) => void;
loading?: boolean;
}
export function ResponsiveForm({
title,
children,
actions,
onSubmit,
loading = false
}: ResponsiveFormProps) {
const { isMobile, isTablet } = useMobileResponsive();
const classes = getResponsiveClasses(isMobile, isTablet);
return (
<Card className={`${classes.card} w-full`}>
{title && (
<CardHeader className={isMobile ? 'mobile-card-header' : ''}>
<CardTitle className={isMobile ? 'text-lg' : 'text-xl'}>
{title}
</CardTitle>
</CardHeader>
)}
<CardContent className={classes.container}>
<form onSubmit={onSubmit} className="space-y-4">
<div className={`form-grid ${classes.grid} ${classes.spacing}`}>
{children}
</div>
{actions && (
<div className={`flex ${isMobile ? 'flex-col gap-3' : 'justify-end gap-3'} pt-4 border-t border-border`}>
{loading ? (
<div className="flex items-center justify-center p-2">
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-primary"></div>
</div>
) : (
actions
)}
</div>
)}
</form>
</CardContent>
</Card>
);
}
// Responsive form field wrapper
interface ResponsiveFormFieldProps {
label: string;
children: React.ReactNode;
required?: boolean;
error?: string;
className?: string;
}
export function ResponsiveFormField({
label,
children,
required = false,
error,
className = ''
}: ResponsiveFormFieldProps) {
const { isMobile } = useMobileResponsive();
return (
<div className={`form-field ${className}`}>
<label className={`form-label ${isMobile ? 'text-sm' : 'text-base'}`}>
{label}
{required && <span className="text-destructive ml-1">*</span>}
</label>
<div className="mt-1">
{children}
</div>
{error && (
<p className="text-sm text-destructive mt-1">{error}</p>
)}
</div>
);
}
@@ -0,0 +1,122 @@
import React from 'react';
import { MobileNavigation } from './MobileNavigation';
import { useMobileResponsive, getResponsiveClasses } from '@/hooks/useMobileResponsive';
interface ResponsiveLayoutProps {
title: string;
onBack?: () => void;
actions?: React.ReactNode;
filters?: React.ReactNode;
children: React.ReactNode;
className?: string;
}
export function ResponsiveLayout({
title,
onBack,
actions,
filters,
children,
className = ''
}: ResponsiveLayoutProps) {
const { isMobile, isTablet } = useMobileResponsive();
const classes = getResponsiveClasses(isMobile, isTablet);
return (
<div className={`min-h-screen bg-background ${className}`}>
<MobileNavigation
title={title}
onBack={onBack}
actions={actions}
>
{filters}
</MobileNavigation>
<main className={`${classes.container} ${classes.spacing}`}>
{children}
</main>
</div>
);
}
// Responsive grid component
interface ResponsiveGridProps {
children: React.ReactNode;
cols?: {
mobile?: number;
tablet?: number;
desktop?: number;
};
className?: string;
}
export function ResponsiveGrid({
children,
cols = { mobile: 1, tablet: 2, desktop: 3 },
className = ''
}: ResponsiveGridProps) {
const { isMobile, isTablet } = useMobileResponsive();
const gridCols = isMobile
? `grid-cols-${cols.mobile}`
: isTablet
? `grid-cols-${cols.tablet}`
: `grid-cols-${cols.desktop}`;
const gap = isMobile ? 'gap-3' : isTablet ? 'gap-4' : 'gap-6';
return (
<div className={`grid ${gridCols} ${gap} ${className}`}>
{children}
</div>
);
}
// Responsive tabs component
interface ResponsiveTabsProps {
tabs: {
id: string;
label: string;
content: React.ReactNode;
}[];
activeTab: string;
onTabChange: (tabId: string) => void;
className?: string;
}
export function ResponsiveTabs({
tabs,
activeTab,
onTabChange,
className = ''
}: ResponsiveTabsProps) {
const { isMobile } = useMobileResponsive();
return (
<div className={className}>
<div className={`${isMobile ? 'flex flex-col gap-1' : 'flex gap-1'} border-b border-border`}>
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => onTabChange(tab.id)}
className={`
px-4 py-3 text-sm font-medium transition-colors touch-target
${isMobile ? 'w-full justify-start' : 'whitespace-nowrap'}
${activeTab === tab.id
? 'border-b-2 border-primary text-primary'
: 'text-muted-foreground hover:text-foreground'
}
`}
>
{tab.label}
</button>
))}
</div>
<div className="mt-4">
{tabs.find(tab => tab.id === activeTab)?.content}
</div>
</div>
);
}
@@ -0,0 +1,111 @@
import React from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '@/components/ui/sheet';
import { Button } from '@/components/ui/button';
import { X } from 'lucide-react';
import { useMobileResponsive, getModalResponsiveClasses } from '@/hooks/useMobileResponsive';
interface ResponsiveModalProps {
isOpen: boolean;
onClose: () => void;
title: string;
description?: string;
children: React.ReactNode;
footer?: React.ReactNode;
size?: 'sm' | 'md' | 'lg' | 'xl';
}
export function ResponsiveModal({
isOpen,
onClose,
title,
description,
children,
footer,
size = 'md'
}: ResponsiveModalProps) {
const { isMobile } = useMobileResponsive();
const modalClasses = getModalResponsiveClasses(isMobile);
// Mobile: Use Sheet for better mobile experience
if (isMobile) {
return (
<Sheet open={isOpen} onOpenChange={onClose}>
<SheetContent side="bottom" className="h-[90vh] flex flex-col">
<SheetHeader className="shrink-0">
<div className="flex items-center justify-between">
<div className="flex-1">
<SheetTitle className="text-lg font-semibold text-left">
{title}
</SheetTitle>
{description && (
<SheetDescription className="text-sm text-muted-foreground text-left mt-1">
{description}
</SheetDescription>
)}
</div>
<Button
variant="ghost"
size="icon"
onClick={onClose}
className="touch-target"
>
<X className="h-5 w-5" />
</Button>
</div>
</SheetHeader>
<div className="flex-1 overflow-y-auto p-4">
{children}
</div>
{footer && (
<div className="shrink-0 p-4 border-t border-border">
<div className="flex flex-col gap-3">
{footer}
</div>
</div>
)}
</SheetContent>
</Sheet>
);
}
// Desktop/Tablet: Use Dialog
const sizeClasses = {
sm: 'max-w-md',
md: 'max-w-lg',
lg: 'max-w-2xl',
xl: 'max-w-4xl'
};
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className={`${sizeClasses[size]} max-h-[90vh] flex flex-col`}>
<DialogHeader className="shrink-0">
<DialogTitle className="text-xl font-semibold">
{title}
</DialogTitle>
{description && (
<DialogDescription className="text-muted-foreground">
{description}
</DialogDescription>
)}
</DialogHeader>
<div className="flex-1 overflow-y-auto py-4">
{children}
</div>
{footer && (
<div className="shrink-0 pt-4 border-t border-border">
<div className="flex justify-end gap-3">
{footer}
</div>
</div>
)}
</DialogContent>
</Dialog>
);
}
@@ -0,0 +1,81 @@
import React from 'react';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Card, CardContent } from '@/components/ui/card';
import { useMobileResponsive, getTableResponsiveClasses } from '@/hooks/useMobileResponsive';
interface ResponsiveTableProps {
headers: string[];
data: any[];
renderRow: (item: any, index: number) => React.ReactNode;
renderMobileCard?: (item: any, index: number) => React.ReactNode;
emptyMessage?: string;
loading?: boolean;
}
export function ResponsiveTable({
headers,
data,
renderRow,
renderMobileCard,
emptyMessage = "Nenhum item encontrado",
loading = false
}: ResponsiveTableProps) {
const { isMobile } = useMobileResponsive();
const tableClasses = getTableResponsiveClasses(isMobile);
if (loading) {
return (
<div className="flex items-center justify-center p-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
);
}
if (data.length === 0) {
return (
<div className="flex items-center justify-center p-8 text-muted-foreground">
{emptyMessage}
</div>
);
}
// Mobile card view
if (isMobile && renderMobileCard) {
return (
<div className="space-y-3">
{data.map((item, index) => (
<Card key={index} className="mobile-card">
<CardContent className="p-4">
{renderMobileCard(item, index)}
</CardContent>
</Card>
))}
</div>
);
}
// Desktop/Tablet table view
return (
<div className={tableClasses.container}>
<Table className={tableClasses.table}>
<TableHeader>
<TableRow>
{headers.map((header, index) => (
<TableHead key={index} className={tableClasses.header}>
{header}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{data.map((item, index) => (
<TableRow key={index}>
{renderRow(item, index)}
</TableRow>
))}
</TableBody>
</Table>
</div>
);
}