Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import LanguageSwitcher from './LanguageSwitcher';
import Sidebar from './Sidebar';

const Header: React.FC = () => {
const [isSidebarOpen, setIsSidebarOpen] = React.useState(false);
const location = useLocation();
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const { theme, toggleTheme } = useTheme();
Expand Down
124 changes: 0 additions & 124 deletions src/components/Timeline.tsx

This file was deleted.

30 changes: 30 additions & 0 deletions src/components/Timeline/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import TimelineStep from './TimelineStep';
import { TimelineEvent } from './types';

interface TimelineProps {
events: TimelineEvent[];
globalCertifications?: string;
}

const Timeline: React.FC<TimelineProps> = ({ events, globalCertifications }) => {
return (
<div className="relative">
{/* Vertical line */}
<div className="absolute left-7 top-0 bottom-0 w-0.5 bg-gray-200" />

<div className="space-y-8 pb-8">
{events.map((event, index) => (
<TimelineStep
key={index}
event={event}
index={index}
globalCertifications={globalCertifications}
/>
))}
</div>
</div>
);
};

export default Timeline;
117 changes: 117 additions & 0 deletions src/components/Timeline/TimelineStep.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
Sprout,
Truck,
Store,
ChevronDown,
Building
} from 'lucide-react';
import TimelineStepDetails from './TimelineStepDetails';
import { TimelineEvent } from './types';

interface TimelineStepProps {
event: TimelineEvent;
index: number;
globalCertifications?: string;
}

const stageIconMap: Record<string, any> = {
farmer: Sprout,
transport: Truck,
retailer: Store,
mandi: Building,
};

const statusStyles: Record<string, { dot: string; badge: string }> = {
completed: {
dot: "bg-green-500",
badge: "text-green-600 bg-green-100",
},
pending: {
dot: "bg-gray-400",
badge: "text-gray-600 bg-gray-100",
},
flagged: {
dot: "bg-red-500",
badge: "text-red-600 bg-red-100",
},
};

const TimelineStep: React.FC<TimelineStepProps> = ({ event, index, globalCertifications }) => {
const [open, setOpen] = useState(false);

// Fallback for icon and styles
const Icon = stageIconMap[event.stage as string] || Store;
const status = event.status || 'completed';
const styles = statusStyles[status];

// Certifications can come from event or global
const certs = event.certifications || globalCertifications;

return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.15 }}
className="relative flex items-start gap-4 sm:gap-6"
>
{/* Timeline Dot */}
<div
className={`z-10 h-14 w-14 rounded-full flex items-center justify-center ${styles.dot} shadow-lg shrink-0`}
>
<Icon className="h-6 w-6 text-white" />
</div>

{/* Card */}
<div
className={`flex-1 bg-white rounded-xl border p-4 sm:p-6 shadow-sm hover:shadow-md transition-all cursor-pointer ${open ? 'ring-2 ring-green-100' : ''}`}
onClick={() => setOpen(!open)}
>
<div className="flex items-center justify-between">
<div className="flex flex-col sm:flex-row sm:items-center gap-2">
<h3 className="text-lg font-semibold capitalize text-gray-800">
{event.stage}
</h3>
<span
className={`text-xs font-medium px-2 py-0.5 rounded-full uppercase w-fit ${styles.badge}`}
>
{status}
</span>
</div>

{/* Arrow Rotation */}
<motion.div
animate={{ rotate: open ? 180 : 0 }}
transition={{ duration: 0.2 }}
>
<ChevronDown className="h-5 w-5 text-gray-400" />
</motion.div>
</div>

{/* Expandable Section */}
<AnimatePresence>
{open && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25 }}
className="overflow-hidden"
>
<TimelineStepDetails
actor={event.actor}
location={event.location}
timestamp={event.timestamp}
certifications={certs}
notes={event.notes}
/>
</motion.div>
)}
</AnimatePresence>
</div>
</motion.div>
);
};

export default TimelineStep;
62 changes: 62 additions & 0 deletions src/components/Timeline/TimelineStepDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react';
import { Clock, User, MapPin, Award } from 'lucide-react';

interface TimelineStepDetailsProps {
actor: string;
location: string;
timestamp: string;
certifications?: string;
notes?: string;
}

const TimelineStepDetails: React.FC<TimelineStepDetailsProps> = ({
actor,
location,
timestamp,
certifications,
notes,
}) => {
const formatDate = (dateString: string) =>
new Date(dateString).toLocaleString("en-US", {
dateStyle: "medium",
timeStyle: "short",
});

return (
<div className="mt-4 space-y-3 text-sm text-gray-600 border-t pt-4">
<div className="flex items-center gap-2">
<Clock className="h-4 w-4 text-gray-400" />
<span className="font-medium">Timestamp:</span>
<span>{formatDate(timestamp)}</span>
</div>

<div className="flex items-center gap-2">
<User className="h-4 w-4 text-green-600" />
<span className="font-medium">Actor:</span>
<span>{actor}</span>
</div>

<div className="flex items-center gap-2">
<MapPin className="h-4 w-4 text-blue-600" />
<span className="font-medium">Location:</span>
<span>{location}</span>
</div>

{certifications && (
<div className="flex items-center gap-2">
<Award className="h-4 w-4 text-yellow-600" />
<span className="font-medium">Certifications:</span>
<span>{certifications}</span>
</div>
)}

{notes && (
<div className="bg-gray-50 rounded-lg p-3 text-gray-700 mt-2">
{notes}
</div>
)}
</div>
);
};

export default TimelineStepDetails;
3 changes: 3 additions & 0 deletions src/components/Timeline/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Timeline from './Timeline';
export default Timeline;
export * from './types';
12 changes: 12 additions & 0 deletions src/components/Timeline/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type Stage = "farmer" | "transport" | "retailer" | "mandi" | string;
export type Status = "completed" | "pending" | "flagged";

export interface TimelineEvent {
stage: Stage;
actor: string;
location: string;
timestamp: string;
status?: Status;
notes?: string;
certifications?: string;
}
1 change: 1 addition & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { AuthProvider } from './context/AuthContext';
import { ThemeProvider } from './context/ThemeContext';
import './i18n/config'; // Initialize i18n
import { AuthProvider } from './context/AuthContext.tsx';
Expand Down
4 changes: 3 additions & 1 deletion src/pages/TrackBatch.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { useState } from 'react';
import { Search, QrCode, Package, Calendar, MapPin, User, FileText } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { cropBatchService } from '../services/cropBatchService';
import Timeline from '../components/Timeline';
import { realCropBatchService } from '../services/realCropBatchService';
// import Timeline from '../components/Timeline';
import QRScanner from '../components/QRScanner';
Expand Down Expand Up @@ -193,7 +195,7 @@ const TrackBatch: React.FC = () => {
<FileText className="h-6 w-6 mr-3 text-green-600 dark:text-green-400" />
Supply Chain Journey
</h3>
{/* <Timeline events={batch.updates} /> */}
<Timeline events={batch.updates} globalCertifications={batch.certifications} />
</div>

{/* QR Code */}
Expand Down
Loading