🚀 Initial commit: Versão atual do TrackSteel APP
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user