@ragavkumarv/training-schedule-planner
v1.1.0
Published
A reusable React component for displaying training schedules with calendar and list views
Downloads
20
Maintainers
Readme
Training Schedule Component
A beautiful, reusable React component for displaying training schedules with calendar and list views. Built with TypeScript, Tailwind CSS, and Radix UI.
Features
- 📅 Calendar View: Visual calendar layout showing training days and holidays
- 📋 List View: Clean list format with day numbers and dates
- 📊 Statistics Cards: Overview of total days, sessions, and holidays
- 🎨 Beautiful UI: Modern design with Tailwind CSS
- ♿ Accessible: Built with Radix UI primitives
- 📱 Responsive: Works on all screen sizes
- 🔧 TypeScript: Full type safety
Installation
npm install @ragavkumarv/training-schedule-plannerPeer Dependencies
Make sure you have these installed in your project:
npm install react react-dom date-fns lucide-reactQuick Start
1. Create a New React App (if needed)
# Using Create React App
npx create-react-app my-training-app --template typescript
cd my-training-app
# Or using Next.js
npx create-next-app@latest my-training-app --typescript --tailwind --eslint
cd my-training-app
# Or using Vite
npm create vite@latest my-training-app -- --template react-ts
cd my-training-app2. Install the Package
npm install @ragavkumarv/training-schedule-planner3. Install Peer Dependencies
npm install react react-dom date-fns lucide-react react-day-picker4. Setup Tailwind CSS (if not already installed)
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -pComplete Setup Guide
Tailwind Configuration
Update your tailwind.config.js:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
// Add this line to include the package components
"./node_modules/@ragavkumarv/training-schedule-planner/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
},
},
plugins: [],
}CSS Variables Setup
Add these CSS variables to your global CSS file (e.g., src/index.css or app/globals.css):
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}Usage Examples
Basic Usage - Display Only
import React from 'react';
import { TrainingSchedule, GeneratedSchedule } from '@ragavkumarv/training-schedule-planner';
const schedule: GeneratedSchedule = {
trainingDays: [
{ date: new Date('2024-01-15'), dayNumber: 1 },
{ date: new Date('2024-01-16'), dayNumber: 2 },
{ date: new Date('2024-01-17'), dayNumber: 3 },
// ... more training days
],
startDate: new Date('2024-01-15'),
endDate: new Date('2024-01-30'),
totalDays: 10,
skippedHolidays: [new Date('2024-01-26')], // Republic Day
};
function App() {
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">My Training Schedule</h1>
<TrainingSchedule schedule={schedule} />
</div>
);
}
export default App;Complete App with Form
import React, { useState } from 'react';
import {
TrainingSchedule,
TrainingForm,
GeneratedSchedule
} from '@ragavkumarv/training-schedule-planner';
function App() {
const [schedule, setSchedule] = useState<GeneratedSchedule | null>(null);
const handleGenerateSchedule = (newSchedule: GeneratedSchedule) => {
setSchedule(newSchedule);
};
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto px-4 py-8">
<div className="text-center mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-2">
Training Schedule Planner
</h1>
<p className="text-lg text-gray-600">
Plan your training sessions with holidays and custom schedules
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Form Section */}
<div className="space-y-6">
<TrainingForm onGenerateSchedule={handleGenerateSchedule} />
</div>
{/* Schedule Display Section */}
<div className="space-y-6">
{schedule ? (
<TrainingSchedule schedule={schedule} />
) : (
<div className="flex items-center justify-center h-96 bg-white rounded-lg border border-gray-200">
<div className="text-center">
<div className="text-6xl mb-4">📅</div>
<h3 className="text-xl font-semibold text-gray-700 mb-2">
No Schedule Generated
</h3>
<p className="text-gray-500">
Fill out the form to generate your training schedule
</p>
</div>
</div>
)}
</div>
</div>
</div>
</div>
);
}
export default App;Next.js App Router Example
Create app/training/page.tsx:
"use client";
import { useState } from "react";
import {
TrainingSchedule,
TrainingForm,
GeneratedSchedule
} from "@ragavkumarv/training-schedule-planner";
export default function TrainingPage() {
const [schedule, setSchedule] = useState<GeneratedSchedule | null>(null);
const handleGenerateSchedule = (newSchedule: GeneratedSchedule) => {
setSchedule(newSchedule);
};
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50">
<div className="container mx-auto px-4 py-8">
<div className="text-center mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-2">
Training Schedule Planner
</h1>
<p className="text-lg text-gray-600">
Plan your training sessions with holidays and custom schedules
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div className="space-y-6">
<TrainingForm onGenerateSchedule={handleGenerateSchedule} />
</div>
<div className="space-y-6">
{schedule ? (
<TrainingSchedule schedule={schedule} />
) : (
<div className="flex items-center justify-center h-96 bg-white/50 backdrop-blur-sm rounded-lg border border-gray-200">
<div className="text-center">
<div className="text-6xl mb-4">📅</div>
<h3 className="text-xl font-semibold text-gray-700 mb-2">
No Schedule Generated
</h3>
<p className="text-gray-500">
Fill out the form to generate your training schedule
</p>
</div>
</div>
)}
</div>
</div>
</div>
</div>
);
}
## Tailwind CSS Setup
This component requires Tailwind CSS. Add these to your `tailwind.config.js`:
```js
module.exports = {
content: [
// ... your existing content paths
"./node_modules/@ragavkumarv/training-schedule-planner/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
},
},
plugins: [],
}And add these CSS variables to your global CSS:
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}Component API
TrainingForm
The TrainingForm component provides a complete form interface for creating training schedules.
Props:
onGenerateSchedule: (schedule: GeneratedSchedule) => void- Callback function called when a schedule is generated
Features:
- Date picker for start date
- Number input for training days
- Weekday selection checkboxes
- Holiday and break date range picker
- Form validation
TrainingSchedule
The TrainingSchedule component displays generated training schedules in multiple views.
Props:
schedule: GeneratedSchedule- The schedule object to display
Features:
- Calendar view with visual indicators
- List view with detailed information
- Statistics cards showing totals
- Holiday highlighting
- Responsive design
Advanced Usage
Custom Styling
You can customize the appearance by overriding CSS variables:
:root {
--primary: 220 14.3% 95.9%; /* Custom primary color */
--primary-foreground: 220.9 39.3% 11%;
--radius: 0.75rem; /* Custom border radius */
}Using Individual Components
You can also import and use individual UI components:
import {
Card,
CardContent,
CardHeader,
CardTitle,
Button,
Calendar
} from '@ragavkumarv/training-schedule-planner';
function MyCustomComponent() {
return (
<Card>
<CardHeader>
<CardTitle>Custom Card</CardTitle>
</CardHeader>
<CardContent>
<Button>Click me</Button>
</CardContent>
</Card>
);
}Using the Schedule Generator
You can use the schedule generation logic independently:
import {
generateSchedule,
TrainingConfig,
GeneratedSchedule
} from '@ragavkumarv/training-schedule-planner';
const config: TrainingConfig = {
startDate: new Date('2024-01-15'),
numberOfDays: 20,
holidays: [
{ id: '1', date: new Date('2024-01-26') }
],
dateRanges: [],
weekdays: {
sun: false,
mon: true,
tue: true,
wed: true,
thu: true,
fri: true,
sat: false
}
};
const schedule: GeneratedSchedule = generateSchedule(config);Troubleshooting
Common Issues
1. Styles not appearing:
- Ensure Tailwind CSS is properly configured
- Check that the package path is included in your
tailwind.config.js - Verify CSS variables are defined in your global CSS
2. TypeScript errors:
- Make sure all peer dependencies are installed
- Check that your TypeScript version is compatible
3. Calendar not working:
- Ensure
react-day-pickeris installed as a peer dependency - Check that date-fns is properly installed
4. Build errors:
- Verify all peer dependencies are installed
- Check for version conflicts in your package.json
Getting Help
If you encounter issues:
- Check the GitHub Issues
- Review the troubleshooting section above
- Ensure all setup steps are completed correctly
Framework-Specific Notes
Next.js
- Use
"use client"directive for components that use state - Ensure Tailwind is configured in
tailwind.config.js - Add CSS variables to
app/globals.css
Create React App
- Install Tailwind CSS separately if not included
- Add CSS variables to
src/index.css - No additional configuration needed
Vite
- Install Tailwind CSS plugin
- Configure PostCSS if needed
- Add CSS variables to your main CSS file
Types
GeneratedSchedule
interface GeneratedSchedule {
trainingDays: TrainingDay[];
startDate: Date;
endDate: Date;
totalDays: number;
skippedHolidays: Date[];
}TrainingDay
interface TrainingDay {
date: Date;
dayNumber: number;
}Customization
The component uses Tailwind CSS classes and can be customized by:
- CSS Variables: Modify the color scheme using CSS custom properties
- Tailwind Classes: Override classes using Tailwind's utility classes
- Component Props: Pass custom className props to the main component
Development
To build the package:
npm run buildTo watch for changes during development:
npm run devLicense
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
