VibeCoderzVibeCoderz
Telegram
All Prompts
AssetDetailPage UI Preview

AssetDetailPage

AssetDetailPage: детальнее о каждом активе. Отображает полную информацию и характеристики объекта для удобного анализа и принятия решений.

by BourneLive Preview

Prompt

# AssetDetailPage

You are given a task to integrate an existing React component in the codebase

~~~/README.md
# Asset Detail Page

A comprehensive, production-grade asset detail view component designed for enterprise resource planning (ERP) or asset management systems.

## Features

- **Rich Header**: Breadcrumbs, status indicators, and primary action buttons.
- **Responsive Grid Layout**: Adaptive layout that works well on desktop and tablet.
- **Detailed Visuals**: Prominent asset image with overlay actions.
- **Categorized Information**:
  - **Summary**: Key stats and location info.
  - **Specs**: Technical specifications in a clean grid.
  - **Ownership**: Clear owner attribution and contact info.
  - **Location**: Detailed physical location tracking.
- **Timeline/History**: Vertical activity log showing lifecycle events.
- **Key Dates**: High-contrast card for critical dates (warranty, maintenance).
- **Documents**: File attachment list.

## Usage

```tsx
import { AssetDetailPage, Asset } from '@/sd-components/debb9ff1-feda-40e6-a8ec-cc9b7dddb5a0';

const myAsset: Asset = {
  id: '123',
  name: 'Generator X',
  status: 'active',
  // ... other fields
};

function MyPage() {
  return (
    <AssetDetailPage 
      asset={myAsset}
      onBack={() => history.back()}
      onEdit={(asset) => console.log('Edit', asset)}
    />
  );
}
```

## Props

| Prop | Type | Description |
|------|------|-------------|
| `asset` | `Asset` | The full asset data object to display |
| `onBack` | `() => void` | Callback for the back button |
| `onEdit` | `(asset: Asset) => void` | Callback for the edit action |
| `onDelete` | `(asset: Asset) => void` | Callback for the delete action |
| `onExport` | `(asset: Asset) => void` | Callback for the export action |

## Types

### Asset

```typescript
interface Asset {
  id: string;
  name: string;
  type: string;
  serialNumber: string;
  status: 'active' | 'maintenance' | 'retired' | 'draft' | 'warning';
  imageUrl?: string;
  tags: string[];
  location: {
    site: string;
    building: string;
    room: string;
    coordinates?: string;
  };
  owner: {
    name: string;
    department: string;
    email: string;
    avatar?: string;
  };
  specs: Record<string, string>;
  dates: {
    created: string;
    updated: string;
    lastMaintenance: string;
    nextMaintenance: string;
    warrantyExpiration: string;
  };
  history: AssetHistoryEvent[];
}
```
~~~

~~~/src/App.tsx
import React, { useState } from 'react';
import AssetDetailPage, { Asset } from './Component';

/**
 * Demo application for Asset Detail Page
 * Shows the component with a rich mock data object
 */
export default function App() {
  // Mock data representing a complex industrial asset
  const mockAsset: Asset = {
    id: 'AST-2024-8842',
    name: 'Hydraulic Press System X500',
    type: 'Heavy Machinery',
    serialNumber: 'HPX-500-V2-9982',
    status: 'active',
    imageUrl: 'https://images.unsplash.com/photo-1581091226825-a6a2a5aee158?auto=format&fit=crop&q=80&w=2670&ixlib=rb-4.0.3',
    tags: ['High Priority', 'Production Line A', 'Requires Certification', 'Heavy Load'],
    location: {
      site: 'Manufacturing Plant Beta',
      building: 'Sector 7',
      room: 'Assembly Hall Main',
      coordinates: '34.0522° N, 118.2437° W'
    },
    owner: {
      name: 'Robert Chen',
      department: 'Operations & Safety',
      email: 'r.chen@enterprise.com',
      avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=facearea&facepad=2&w=256&h=256&q=80'
    },
    specs: {
      'Manufacturer': 'Industrial Dynamics Corp',
      'Model Year': '2022',
      'Power Rating': '480V 3-Phase',
      'Max Pressure': '5000 PSI',
      'Oil Capacity': '200 Gallons',
      'Weight': '4,500 lbs',
      'Dimensions': '12ft x 8ft x 10ft',
      'Operating Temp': '-10°C to 45°C'
    },
    dates: {
      created: 'Jan 15, 2022',
      updated: 'Oct 24, 2023',
      lastMaintenance: 'Oct 01, 2023',
      nextMaintenance: 'Jan 01, 2024',
      warrantyExpiration: 'Jan 15, 2027'
    },
    history: [
      {
        id: 'evt-1',
        type: 'alert',
        title: 'Pressure Warning Sensor B',
        description: 'Automated alert: Hydraulic pressure fluctuation detected above threshold.',
        user: 'System Monitor',
        date: 'Oct 23, 2023 • 14:30'
      },
      {
        id: 'evt-2',
        type: 'maintenance',
        title: 'Quarterly Servicing Completed',
        description: 'Fluids replaced, seals checked, calibration verified. All nominal.',
        user: 'Sarah Miller (Tech)',
        date: 'Oct 01, 2023 • 09:15'
      },
      {
        id: 'evt-3',
        type: 'assignment',
        title: 'Transfer to Sector 7',
        description: 'Physical relocation and re-commissioning in new assembly wing.',
        user: 'Robert Chen',
        date: 'Sep 15, 2023 • 11:00'
      },
      {
        id: 'evt-4',
        type: 'update',
        title: 'Firmware Update v2.1',
        description: 'Control panel software updated to latest security patch.',
        user: 'IT Admin',
        date: 'Aug 20, 2023 • 16:45'
      }
    ]
  };

  const handleEdit = (asset: Asset) => {
    console.log('Edit clicked for', asset.id);
    alert(`Edit mode for ${asset.name}`);
  };

  const handleBack = () => {
    console.log('Back clicked');
  };

  return (
    <div className="bg-slate-100 min-h-screen">
      <AssetDetailPage 
        asset={mockAsset} 
        onBack={handleBack}
        onEdit={handleEdit}
        onExport={(a) => console.log('Exporting', a.name)}
      />
    </div>
  );
}
~~~

~~~/package.json
{
  "name": "asset-detail-page",
  "version": "1.0.0",
  "description": "A production-grade asset detail page component",
  "main": "src/Component.tsx",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "lucide-react": "^0.344.0",
    "date-fns": "^3.3.1",
    "clsx": "^2.1.0",
    "tailwind-merge": "^2.2.1"
  }
}
~~~

~~~/src/Component.tsx
import React from 'react';
import { 
  ArrowLeft, 
  Printer, 
  Share2, 
  Pencil, 
  Trash2, 
  Box, 
  MapPin, 
  User, 
  Calendar, 
  Tag, 
  Activity, 
  FileText,
  Clock,
  ShieldCheck,
  AlertTriangle,
  History,
  MoreHorizontal,
  ChevronRight,
  Download
} from 'lucide-react';

export type AssetStatus = 'active' | 'maintenance' | 'retired' | 'draft' | 'warning';

export interface AssetHistoryEvent {
  id: string;
  type: 'update' | 'maintenance' | 'assignment' | 'alert';
  title: string;
  description: string;
  user: string;
  date: string;
}

export interface Asset {
  id: string;
  name: string;
  type: string;
  serialNumber: string;
  status: AssetStatus;
  imageUrl?: string;
  tags: string[];
  location: {
    site: string;
    building: string;
    room: string;
    coordinates?: string;
  };
  owner: {
    name: string;
    department: string;
    email: string;
    avatar?: string;
  };
  specs: Record<string, string>;
  dates: {
    created: string;
    updated: string;
    lastMaintenance: string;
    nextMaintenance: string;
    warrantyExpiration: string;
  };
  history: AssetHistoryEvent[];
}

interface AssetDetailPageProps {
  asset: Asset;
  onBack?: () => void;
  onEdit?: (asset: Asset) => void;
  onDelete?: (asset: Asset) => void;
  onExport?: (asset: Asset) => void;
}

/**
 * Helper to render status badge with appropriate styling
 */
const StatusBadge = ({ status }: { status: AssetStatus }) => {
  const styles = {
    active: 'bg-emerald-50 text-emerald-700 border-emerald-200 ring-emerald-500/20',
    maintenance: 'bg-amber-50 text-amber-700 border-amber-200 ring-amber-500/20',
    retired: 'bg-slate-50 text-slate-700 border-slate-200 ring-slate-500/20',
    draft: 'bg-blue-50 text-blue-700 border-blue-200 ring-blue-500/20',
    warning: 'bg-red-50 text-red-700 border-red-200 ring-red-500/20',
  };

  const labels = {
    active: 'Operational',
    maintenance: 'In Maintenance',
    retired: 'Decommissioned',
    draft: 'Draft',
    warning: 'Attention Required',
  };

  const icons = {
    active: ShieldCheck,
    maintenance: Clock,
    retired: Box,
    draft: FileText,
    warning: AlertTriangle,
  };

  const Icon = icons[status];

  return (
    <span className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium border ring-1 ring-inset ${styles[status]}`}>
      <Icon className="w-3.5 h-3.5" />
      {labels[status]}
    </span>
  );
};

/**
 * Asset Detail Page Component
 * Displays comprehensive information about a single asset.
 */
export function AssetDetailPage({ asset, onBack, onEdit, onDelete, onExport }: AssetDetailPageProps) {
  return (
    <div 
      className="min-h-screen bg-background text-foreground font-sans pb-12"
      style={{
        "--primary": "209 100% 54.7%",
        "--primary-foreground": "0 0% 100%",
      } as React.CSSProperties}
    >
      {/* Top Navigation / Breadcrumbs */}
      <div className="sticky top-0 z-30 bg-background/80 backdrop-blur-md border-b border-border px-6 py-4">
        <div className="max-w-7xl mx-auto flex items-center justify-between">
          <div className="flex items-center gap-4">
            <button 
              onClick={onBack}
              className="p-2 -ml-2 text-muted-foreground hover:text-foreground hover:bg-muted/50 rounded-lg transition-colors"
            >
              <ArrowLeft className="w-5 h-5" />
            </button>
            <nav className="hidden sm:flex items-center text-sm text-muted-foreground">
              <span className="hover:text-foreground cursor-pointer transition-colors">Assets</span>
              <ChevronRight className="w-4 h-4 mx-2 text-muted-foreground/50" />
              <span className="hover:text-foreground cursor-pointer transition-colors">{asset.type}s</span>
              <ChevronRight className="w-4 h-4 mx-2 text-muted-foreground/50" />
              <span className="text-foreground font-medium truncate max-w-[200px]">{asset.name}</span>
            </nav>
          </div>
          
          <div className="flex items-center gap-2">
            <button 
              onClick={() => onExport?.(asset)}
              className="hidden sm:flex items-center gap-2 px-3 py-2 text-sm font-medium text-muted-foreground bg-card border border-border rounded-lg hover:bg-muted/50 hover:text-foreground transition-all shadow-sm"
            >
              <Download className="w-4 h-4" />
              Export
            </button>
            <div className="h-6 w-px bg-border mx-1 hidden sm:block" />
            <button 
              onClick={() => onEdit?.(asset)}
              className="flex items-center gap-2 px-3 py-2 text-sm font-medium text-primary-foreground bg-primary border border-transparent rounded-lg hover:bg-primary/90 transition-all shadow-sm"
            >
              <Pencil className="w-4 h-4" />
              <span className="hidden sm:inline">Edit Asset</span>
            </button>
            <button className="sm:hidden p-2 text-muted-foreground hover:bg-muted/50 rounded-lg">
              <MoreHorizontal className="w-5 h-5" />
            </button>
          </div>
        </div>
      </div>

      <main className="max-w-7xl mx-auto px-4 sm:px-6 py-8">
        {/* Header Section */}
        <div className="mb-8">
          <div className="flex flex-col md:flex-row md:items-start md:justify-between gap-6">
            <div className="flex-1">
              <div className="flex flex-wrap items-center gap-3 mb-3">
                <StatusBadge status={asset.status} />
                <span className="text-xs font-mono text-muted-foreground px-2 py-0.5 bg-muted rounded border border-border">
                  ID: {asset.id}
                </span>
                <span className="text-xs font-mono text-muted-foreground px-2 py-0.5 bg-muted rounded border border-border">
                  SN: {asset.serialNumber}
                </span>
              </div>
              <h1 className="text-3xl font-bold text-foreground tracking-tight mb-2">{asset.name}</h1>
              <div className="flex flex-wrap items-center gap-6 text-sm text-muted-foreground">
                <div className="flex items-center gap-2">
                  <Box className="w-4 h-4 text-muted-foreground/70" />
                  <span>{asset.type}</span>
                </div>
                <div className="flex items-center gap-2">
                  <MapPin className="w-4 h-4 text-muted-foreground/70" />
                  <span>{asset.location.site} • {asset.location.room}</span>
                </div>
                <div className="flex items-center gap-2">
                  <Clock className="w-4 h-4 text-muted-foreground/70" />
                  <span>Updated {asset.dates.updated}</span>
                </div>
              </div>
            </div>
            
            {/* Quick Actions / Stats Card - Mobile only shows actions above, desktop shows high-level stats here */}
            <div className="hidden md:flex gap-4">
              <div className="px-5 py-3 bg-card rounded-xl border border-border shadow-sm">
                <div className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">Total Uptime</div>
                <div className="text-xl font-bold text-emerald-600">99.8%</div>
              </div>
              <div className="px-5 py-3 bg-card rounded-xl border border-border shadow-sm">
                <div className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">Next Service</div>
                <div className="text-xl font-bold text-foreground">{asset.dates.nextMaintenance}</div>
              </div>
            </div>
          </div>
        </div>

        <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
          {/* Main Content Column */}
          <div className="lg:col-span-2 space-y-8">
            
            {/* Image & Key Info */}
            <div className="bg-card rounded-2xl border border-border overflow-hidden shadow-sm">
              <div className="aspect-video w-full bg-muted relative group overflow-hidden">
                {asset.imageUrl ? (
                  <img 
                    src={asset.imageUrl} 
                    alt={asset.name}
                    className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
                  />
                ) : (
                  <div className="w-full h-full flex items-center justify-center text-muted-foreground/30">
                    <Box className="w-20 h-20" />
                  </div>
                )}
                <div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity flex items-end p-6">
                  <button className="text-white text-sm font-medium flex items-center gap-2 hover:underline">
                    <Share2 className="w-4 h-4" /> Share Asset Preview
                  </button>
                </div>
              </div>
              
              <div className="p-6">
                <h3 className="text-lg font-semibold text-foreground mb-4 flex items-center gap-2">
                  <FileText className="w-5 h-5 text-primary" />
                  Description & Notes
                </h3>
                <p className="text-muted-foreground leading-relaxed mb-6">
                  High-performance {asset.name.toLowerCase()} utilized primarily for {asset.type.toLowerCase()} operations. 
                  Currently assigned to the {asset.owner.department} department. 
                  This asset requires quarterly maintenance checks and is covered under the enterprise warranty program until {asset.dates.warrantyExpiration}.
                </p>
                
                <div className="flex flex-wrap gap-2">
                  {asset.tags.map(tag => (
                    <span key={tag} className="inline-flex items-center gap-1 px-2.5 py-1 rounded-md bg-muted text-muted-foreground text-xs font-medium border border-border">
                      <Tag className="w-3 h-3 text-muted-foreground/70" />
                      {tag}
                    </span>
                  ))}
                  <button className="px-2.5 py-1 rounded-md border border-dashed border-border text-muted-foreground text-xs hover:bg-muted/50 transition-colors">
                    + Add Tag
                  </button>
                </div>
              </div>
            </div>

            {/* Technical Specifications Grid */}
            <div className="bg-card rounded-2xl border border-border shadow-sm overflow-hidden">
              <div className="border-b border-border px-6 py-4 bg-muted/30 flex justify-between items-center">
                <h3 className="text-lg font-semibold text-foreground">Technical Specifications</h3>
                <button className="text-sm text-primary font-medium hover:underline">View Manual</button>
              </div>
              <div className="grid grid-cols-1 sm:grid-cols-2 divide-y sm:divide-y-0 sm:divide-x divide-border">
                {Object.entries(asset.specs).map(([key, value], idx) => (
                  <div key={key} className={`p-5 hover:bg-muted/30 transition-colors`}>
                    <dt className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">{key}</dt>
                    <dd className="text-sm font-medium text-foreground">{value}</dd>
                  </div>
                ))}
              </div>
            </div>

            {/* Location & Assignment */}
            <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
               <div className="bg-card rounded-2xl border border-border shadow-sm p-6">
                  <h3 className="text-base font-semibold text-foreground mb-4 flex items-center gap-2">
                    <User className="w-4 h-4 text-purple-500" />
                    Responsibility
                  </h3>
                  <div className="flex items-start gap-4">
                    {asset.owner.avatar ? (
                      <img src={asset.owner.avatar} alt={asset.owner.name} className="w-12 h-12 rounded-full border-2 border-card shadow-sm" />
                    ) : (
                      <div className="w-12 h-12 rounded-full bg-purple-100 flex items-center justify-center text-purple-600 font-bold text-lg">
                        {asset.owner.name.charAt(0)}
                      </div>
                    )}
                    <div>
                      <div className="font-medium text-foreground">{asset.owner.name}</div>
                      <div className="text-sm text-muted-foreground mb-2">{asset.owner.department}</div>
                      <a href={`mailto:${asset.owner.email}`} className="text-sm text-primary hover:underline">
                        {asset.owner.email}
                      </a>
                    </div>
                  </div>
               </div>

               <div className="bg-card rounded-2xl border border-border shadow-sm p-6">
                  <h3 className="text-base font-semibold text-foreground mb-4 flex items-center gap-2">
                    <MapPin className="w-4 h-4 text-red-500" />
                    Storage Location
                  </h3>
                  <div className="space-y-3">
                    <div className="flex justify-between items-center text-sm">
                      <span className="text-muted-foreground">Site</span>
                      <span className="font-medium text-foreground">{asset.location.site}</span>
                    </div>
                    <div className="flex justify-between items-center text-sm">
                      <span className="text-muted-foreground">Building</span>
                      <span className="font-medium text-foreground">{asset.location.building}</span>
                    </div>
                    <div className="flex justify-between items-center text-sm">
                      <span className="text-muted-foreground">Room/Area</span>
                      <span className="font-medium text-foreground">{asset.location.room}</span>
                    </div>
                    {asset.location.coordinates && (
                      <div className="pt-2 border-t border-border mt-2">
                         <div className="text-xs font-mono text-muted-foreground truncate bg-muted p-1.5 rounded text-center">
                           {asset.location.coordinates}
                         </div>
                      </div>
                    )}
                  </div>
               </div>
            </div>

          </div>

          {/* Sidebar Column */}
          <div className="space-y-6">
            
            {/* Timeline / Activity */}
            <div className="bg-card rounded-2xl border border-border shadow-sm flex flex-col h-full max-h-[600px]">
              <div className="p-5 border-b border-border flex justify-between items-center">
                <h3 className="font-semibold text-foreground flex items-center gap-2">
                  <Activity className="w-4 h-4 text-muted-foreground" />
                  Activity Log
                </h3>
                <button className="text-xs font-medium text-muted-foreground hover:text-foreground">View All</button>
              </div>
              <div className="p-5 overflow-y-auto custom-scrollbar flex-1">
                <div className="space-y-6 relative before:absolute before:inset-y-0 before:left-[17px] before:w-px before:bg-border">
                  {asset.history.map((event) => (
                    <div key={event.id} className="relative pl-10">
                      <div className={`absolute left-0 top-1 w-9 h-9 rounded-full border-4 border-card shadow-sm flex items-center justify-center text-white
                        ${event.type === 'maintenance' ? 'bg-amber-500' : 
                          event.type === 'alert' ? 'bg-red-500' : 
                          event.type === 'assignment' ? 'bg-purple-500' : 'bg-primary'
                        }`}
                      >
                         {event.type === 'maintenance' ? <Clock className="w-4 h-4" /> :
                          event.type === 'alert' ? <AlertTriangle className="w-4 h-4" /> :
                          event.type === 'assignment' ? <User className="w-4 h-4" /> : <FileText className="w-4 h-4" />
                         }
                      </div>
                      <div className="flex flex-col">
                        <span className="text-xs text-muted-foreground font-medium mb-0.5">{event.date}</span>
                        <span className="text-sm font-medium text-foreground">{event.title}</span>
                        <span className="text-xs text-muted-foreground mt-0.5">{event.description}</span>
                        <div className="mt-1.5 flex items-center gap-1.5">
                           <div className="w-4 h-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold text-muted-foreground">
                             {event.user.charAt(0)}
                           </div>
                           <span className="text-xs text-muted-foreground">{event.user}</span>
                        </div>
                      </div>
                    </div>
                  ))}
                </div>
              </div>
              <div className="p-4 border-t border-border bg-muted/30 rounded-b-2xl">
                <button className="w-full py-2 text-sm font-medium text-muted-foreground bg-card border border-border rounded-lg hover:bg-muted/50 hover:text-foreground transition-colors shadow-sm">
                  Add Note or Log Event
                </button>
              </div>
            </div>

            {/* Quick Stats/Metadata */}
            <div className="bg-primary rounded-2xl shadow-lg p-6 text-primary-foreground overflow-hidden relative">
              <div className="absolute top-0 right-0 -mr-4 -mt-4 w-24 h-24 bg-white/10 rounded-full blur-2xl"></div>
              <h3 className="font-semibold text-primary-foreground/90 mb-4 flex items-center gap-2">
                <Calendar className="w-4 h-4 text-primary-foreground/70" />
                Key Dates
              </h3>
              <div className="space-y-4 relative z-10">
                <div>
                   <div className="text-xs text-primary-foreground/60 uppercase tracking-wider mb-1">Commissioned</div>
                   <div className="font-medium">{asset.dates.created}</div>
                </div>
                <div>
                   <div className="text-xs text-primary-foreground/60 uppercase tracking-wider mb-1">Warranty Expires</div>
                   <div className="font-medium text-amber-300">{asset.dates.warrantyExpiration}</div>
                </div>
                <div>
                   <div className="text-xs text-primary-foreground/60 uppercase tracking-wider mb-1">Last Audit</div>
                   <div className="font-medium">{asset.dates.updated}</div>
                </div>
              </div>
            </div>

            {/* Associated Documents */}
            <div className="bg-card rounded-2xl border border-border shadow-sm p-6">
              <h3 className="font-semibold text-foreground mb-4 flex items-center gap-2">
                <FileText className="w-4 h-4 text-muted-foreground" />
                Documents
              </h3>
              <ul className="space-y-3">
                {[
                  { name: 'User Manual v2.4.pdf', size: '2.4 MB' },
                  { name: 'Warranty_Certificate.pdf', size: '856 KB' },
                  { name: 'Maintenance_Log_2023.xlsx', size: '1.2 MB' }
                ].map((doc, i) => (
                  <li key={i} className="flex items-center justify-between group cursor-pointer">
                    <div className="flex items-center gap-3">
                      <div className="p-2 bg-primary/10 text-primary rounded-lg group-hover:bg-primary/20 transition-colors">
                        <FileText className="w-4 h-4" />
                      </div>
                      <div>
                        <div className="text-sm font-medium text-foreground group-hover:text-primary transition-colors">{doc.name}</div>
                        <div className="text-xs text-muted-foreground">{doc.size}</div>
                      </div>
                    </div>
                    <Download className="w-4 h-4 text-muted-foreground/50 group-hover:text-muted-foreground" />
                  </li>
                ))}
              </ul>
            </div>

          </div>
        </div>
      </main>
    </div>
  );
}

export default AssetDetailPage;
~~~

Implementation Guidelines

1. Analyze the component structure, styling, animation implementations
2. Review the component's arguments and state
3. Think through what is the best place to adopt this component/style into the design we are doing
4. Then adopt the component/design to our current system

Help me integrate this into my design
All Prompts