POWR/components/templates/TemplateDetails.tsx
2025-02-19 21:39:47 -05:00

373 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// components/templates/TemplateDetails.tsx
import React from 'react';
import { View, ScrollView } from 'react-native';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import { Text } from '@/components/ui/text';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
import {
Edit2,
Dumbbell,
Target,
Calendar,
Hash,
ClipboardList,
Settings
} from 'lucide-react-native';
import {
WorkoutTemplate,
TemplateSource,
getSourceDisplay
} from '@/types/templates';
import { useTheme } from '@react-navigation/native';
import type { CustomTheme } from '@/lib/theme';
const Tab = createMaterialTopTabNavigator();
interface TemplateDetailsProps {
template: WorkoutTemplate;
open: boolean;
onOpenChange: (open: boolean) => void;
onEdit?: () => void;
}
// Overview Tab Component
function OverviewTab({ template, onEdit }: { template: WorkoutTemplate; onEdit?: () => void }) {
const {
title,
type,
category,
description,
exercises = [],
tags = [],
metadata,
availability // Replace source with availability
} = template;
// Calculate source type from availability
const sourceType = availability.source.includes('nostr')
? 'nostr'
: availability.source.includes('powr')
? 'powr'
: 'local';
return (
<ScrollView
className="flex-1 px-4"
contentContainerStyle={{ paddingBottom: 20 }}
>
<View className="gap-6 py-4">
{/* Basic Info Section */}
<View className="flex-row items-center gap-2">
<Badge
variant={sourceType === 'local' ? 'outline' : 'secondary'}
className="capitalize"
>
<Text>{getSourceDisplay(template)}</Text>
</Badge>
<Badge
variant="outline"
className="capitalize bg-muted"
>
<Text>{type}</Text>
</Badge>
</View>
<Separator className="bg-border" />
{/* Category Section */}
<View className="flex-row items-center gap-2">
<View className="w-8 h-8 items-center justify-center rounded-md bg-muted">
<Target size={18} className="text-muted-foreground" />
</View>
<View>
<Text className="text-sm text-muted-foreground">Category</Text>
<Text className="text-base font-medium text-foreground">{category}</Text>
</View>
</View>
{/* Description Section */}
{description && (
<View>
<Text className="text-base font-semibold text-foreground mb-2">Description</Text>
<Text className="text-base text-muted-foreground leading-relaxed">{description}</Text>
</View>
)}
{/* Exercises Section */}
<View>
<View className="flex-row items-center gap-2 mb-2">
<Dumbbell size={16} className="text-muted-foreground" />
<Text className="text-base font-semibold text-foreground">Exercises</Text>
</View>
<View className="gap-2">
{exercises.map((exerciseConfig, index) => (
<View key={index} className="bg-card p-3 rounded-lg">
<Text className="text-base font-medium text-foreground">
{exerciseConfig.exercise.title}
</Text>
<Text className="text-sm text-muted-foreground">
{exerciseConfig.targetSets} sets × {exerciseConfig.targetReps} reps
</Text>
{exerciseConfig.notes && (
<Text className="text-sm text-muted-foreground mt-1">
{exerciseConfig.notes}
</Text>
)}
</View>
))}
</View>
</View>
{/* Tags Section */}
{tags.length > 0 && (
<View>
<View className="flex-row items-center gap-2 mb-2">
<Hash size={16} className="text-muted-foreground" />
<Text className="text-base font-semibold text-foreground">Tags</Text>
</View>
<View className="flex-row flex-wrap gap-2">
{tags.map(tag => (
<Badge key={tag} variant="secondary">
<Text>{tag}</Text>
</Badge>
))}
</View>
</View>
)}
{/* Usage Stats Section */}
{metadata && (
<View>
<View className="flex-row items-center gap-2 mb-2">
<Calendar size={16} className="text-muted-foreground" />
<Text className="text-base font-semibold text-foreground">Usage</Text>
</View>
<View className="gap-2">
{metadata.useCount && (
<Text className="text-base text-muted-foreground">
Used {metadata.useCount} times
</Text>
)}
{metadata.lastUsed && (
<Text className="text-base text-muted-foreground">
Last used: {new Date(metadata.lastUsed).toLocaleDateString()}
</Text>
)}
{metadata.averageDuration && (
<Text className="text-base text-muted-foreground">
Average duration: {Math.round(metadata.averageDuration / 60)} minutes
</Text>
)}
</View>
</View>
)}
{/* Edit Button */}
{onEdit && (
<Button
onPress={onEdit}
className="w-full mt-2"
>
<Edit2 size={18} className="mr-2 text-primary-foreground" />
<Text className="text-primary-foreground font-semibold">
Edit Template
</Text>
</Button>
)}
</View>
</ScrollView>
);
}
// Function to format template source for display
function formatTemplateSource(source: TemplateSource | undefined, templateSource: 'local' | 'powr' | 'nostr'): string {
if (!source) {
return templateSource === 'local' ? 'Local Template' : templateSource.toUpperCase();
}
const author = source.authorName || 'Unknown Author';
if (source.version) {
return `Modified from ${author} (v${source.version})`;
}
return `Original by ${author}`;
}
// History Tab Component
function HistoryTab({ template }: { template: WorkoutTemplate }) {
return (
<ScrollView
className="flex-1 px-4"
contentContainerStyle={{ paddingBottom: 20 }}
>
<View className="gap-6 py-4">
{/* Performance Stats */}
<View>
<Text className="text-base font-semibold text-foreground mb-4">Performance Summary</Text>
<View className="flex-row gap-4">
<View className="flex-1 bg-card p-4 rounded-lg">
<Text className="text-sm text-muted-foreground mb-1">Avg. Duration</Text>
<Text className="text-lg font-semibold text-foreground">
{template.metadata?.averageDuration
? `${Math.round(template.metadata.averageDuration / 60)}m`
: '--'}
</Text>
</View>
<View className="flex-1 bg-card p-4 rounded-lg">
<Text className="text-sm text-muted-foreground mb-1">Completion Rate</Text>
<Text className="text-lg font-semibold text-foreground">--</Text>
</View>
</View>
</View>
{/* History List */}
<View>
<Text className="text-base font-semibold text-foreground mb-4">Recent Workouts</Text>
<View className="gap-3">
{/* Placeholder for when no history exists */}
<View className="bg-muted p-8 rounded-lg items-center justify-center">
<ClipboardList size={24} className="text-muted-foreground mb-2" />
<Text className="text-muted-foreground text-center">
No workout history available yet
</Text>
</View>
</View>
</View>
</View>
</ScrollView>
);
}
// Settings Tab Component
function SettingsTab({ template }: { template: WorkoutTemplate }) {
const {
type,
rounds,
duration,
interval,
restBetweenRounds,
} = template;
return (
<ScrollView
className="flex-1 px-4"
contentContainerStyle={{ paddingBottom: 20 }}
>
<View className="gap-6 py-4">
{/* Workout Configuration */}
<View>
<Text className="text-base font-semibold text-foreground mb-4">Workout Settings</Text>
<View className="gap-4">
<View className="bg-card p-4 rounded-lg">
<Text className="text-sm text-muted-foreground mb-1">Type</Text>
<Text className="text-base font-medium text-foreground capitalize">{type}</Text>
</View>
{rounds && (
<View className="bg-card p-4 rounded-lg">
<Text className="text-sm text-muted-foreground mb-1">Rounds</Text>
<Text className="text-base font-medium text-foreground">{rounds}</Text>
</View>
)}
{duration && (
<View className="bg-card p-4 rounded-lg">
<Text className="text-sm text-muted-foreground mb-1">Duration</Text>
<Text className="text-base font-medium text-foreground">
{Math.floor(duration / 60)}:{(duration % 60).toString().padStart(2, '0')}
</Text>
</View>
)}
{interval && (
<View className="bg-card p-4 rounded-lg">
<Text className="text-sm text-muted-foreground mb-1">Interval</Text>
<Text className="text-base font-medium text-foreground">
{Math.floor(interval / 60)}:{(interval % 60).toString().padStart(2, '0')}
</Text>
</View>
)}
{restBetweenRounds && (
<View className="bg-card p-4 rounded-lg">
<Text className="text-sm text-muted-foreground mb-1">Rest Between Rounds</Text>
<Text className="text-base font-medium text-foreground">
{Math.floor(restBetweenRounds / 60)}:{(restBetweenRounds % 60).toString().padStart(2, '0')}
</Text>
</View>
)}
</View>
</View>
</View>
</ScrollView>
);
}
export function TemplateDetails({
template,
open,
onOpenChange,
onEdit
}: TemplateDetailsProps) {
const theme = useTheme() as CustomTheme;
return (
<Sheet isOpen={open} onClose={() => onOpenChange(false)}>
<SheetHeader>
<SheetTitle>
<Text className="text-xl font-bold text-foreground">{template.title}</Text>
</SheetTitle>
</SheetHeader>
<SheetContent>
<View style={{ flex: 1, minHeight: 400 }}>
<Tab.Navigator
style={{ flex: 1 }}
screenOptions={{
tabBarActiveTintColor: theme.colors.tabIndicator,
tabBarInactiveTintColor: theme.colors.tabInactive,
tabBarLabelStyle: {
fontSize: 13,
textTransform: 'capitalize',
fontWeight: 'bold',
marginHorizontal: -4,
},
tabBarIndicatorStyle: {
backgroundColor: theme.colors.tabIndicator,
height: 2,
},
tabBarStyle: {
backgroundColor: theme.colors.background,
elevation: 0,
shadowOpacity: 0,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
},
tabBarPressColor: theme.colors.primary,
}}
>
<Tab.Screen
name="overview"
options={{ title: 'Overview' }}
>
{() => <OverviewTab template={template} onEdit={onEdit} />}
</Tab.Screen>
<Tab.Screen
name="history"
options={{ title: 'History' }}
>
{() => <HistoryTab template={template} />}
</Tab.Screen>
<Tab.Screen
name="settings"
options={{ title: 'Settings' }}
>
{() => <SettingsTab template={template} />}
</Tab.Screen>
</Tab.Navigator>
</View>
</SheetContent>
</Sheet>
);
}