- Next.js 15.2.4 with React 19 and TypeScript - Monthly schedule generator with Excel-like formatting - Complete employee list from example.xlsx (18 employees) - Excel-style column naming (A, B, C, ..., Z, AA, AB, ...) - Counter-clockwise text rotation for data values - Weekend highlighting and shift color coding - Team selection for TKB, METRO, D8 - 7 days from previous month + full current month date range - jspreadsheet integration for Excel-like interface 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
173 lines
6.2 KiB
TypeScript
173 lines
6.2 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Save, Download, Plus } from "lucide-react"
|
|
|
|
interface TimeshiftSpreadsheetProps {
|
|
teamId: string
|
|
teamName: string
|
|
}
|
|
|
|
export function TimeshiftSpreadsheet({ teamId, teamName }: TimeshiftSpreadsheetProps) {
|
|
const spreadsheetRef = React.useRef<HTMLDivElement>(null)
|
|
const jspreadsheetInstance = React.useRef<any>(null)
|
|
|
|
// Sample data for different teams
|
|
const getTeamData = (teamId: string) => {
|
|
const baseData = [
|
|
["Employee", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
|
|
["John Smith", "08:00-16:00", "08:00-16:00", "08:00-16:00", "08:00-16:00", "08:00-16:00", "OFF", "OFF"],
|
|
["Jane Doe", "16:00-00:00", "16:00-00:00", "16:00-00:00", "16:00-00:00", "16:00-00:00", "OFF", "OFF"],
|
|
["Mike Johnson", "00:00-08:00", "00:00-08:00", "00:00-08:00", "00:00-08:00", "00:00-08:00", "OFF", "OFF"],
|
|
["Sarah Wilson", "08:00-16:00", "OFF", "08:00-16:00", "OFF", "08:00-16:00", "08:00-16:00", "08:00-16:00"],
|
|
["", "", "", "", "", "", "", ""],
|
|
["", "", "", "", "", "", "", ""],
|
|
]
|
|
return baseData
|
|
}
|
|
|
|
React.useEffect(() => {
|
|
const loadJSpreadsheet = async () => {
|
|
// Load jspreadsheet CSS and JS
|
|
const cssLink = document.createElement("link")
|
|
cssLink.rel = "stylesheet"
|
|
cssLink.href = "https://bossanova.uk/jspreadsheet/v4/jexcel.css"
|
|
document.head.appendChild(cssLink)
|
|
|
|
const jSuiteCssLink = document.createElement("link")
|
|
jSuiteCssLink.rel = "stylesheet"
|
|
jSuiteCssLink.href = "https://jsuites.net/v4/jsuites.css"
|
|
document.head.appendChild(jSuiteCssLink)
|
|
|
|
// Load JavaScript files
|
|
const jSuitesScript = document.createElement("script")
|
|
jSuitesScript.src = "https://jsuites.net/v4/jsuites.js"
|
|
document.head.appendChild(jSuitesScript)
|
|
|
|
const jSpreadsheetScript = document.createElement("script")
|
|
jSpreadsheetScript.src = "https://bossanova.uk/jspreadsheet/v4/jexcel.js"
|
|
document.head.appendChild(jSpreadsheetScript)
|
|
|
|
// Wait for scripts to load
|
|
await new Promise((resolve) => {
|
|
jSpreadsheetScript.onload = resolve
|
|
})
|
|
|
|
// Initialize spreadsheet
|
|
if (spreadsheetRef.current && (window as any).jexcel) {
|
|
// Clear previous instance
|
|
if (jspreadsheetInstance.current) {
|
|
jspreadsheetInstance.current.destroy()
|
|
}
|
|
|
|
jspreadsheetInstance.current = (window as any).jexcel(spreadsheetRef.current, {
|
|
data: getTeamData(teamId),
|
|
columns: [
|
|
{ type: "text", title: "Employee", width: 120 },
|
|
{ type: "text", title: "Monday", width: 100 },
|
|
{ type: "text", title: "Tuesday", width: 100 },
|
|
{ type: "text", title: "Wednesday", width: 100 },
|
|
{ type: "text", title: "Thursday", width: 100 },
|
|
{ type: "text", title: "Friday", width: 100 },
|
|
{ type: "text", title: "Saturday", width: 100 },
|
|
{ type: "text", title: "Sunday", width: 100 },
|
|
],
|
|
minDimensions: [8, 10],
|
|
allowInsertRow: true,
|
|
allowInsertColumn: false,
|
|
allowDeleteRow: true,
|
|
allowDeleteColumn: false,
|
|
contextMenu: true,
|
|
tableOverflow: true,
|
|
tableWidth: "100%",
|
|
tableHeight: "400px",
|
|
style: {
|
|
A1: "background-color: #f3f4f6; font-weight: bold;",
|
|
B1: "background-color: #f3f4f6; font-weight: bold;",
|
|
C1: "background-color: #f3f4f6; font-weight: bold;",
|
|
D1: "background-color: #f3f4f6; font-weight: bold;",
|
|
E1: "background-color: #f3f4f6; font-weight: bold;",
|
|
F1: "background-color: #f3f4f6; font-weight: bold;",
|
|
G1: "background-color: #f3f4f6; font-weight: bold;",
|
|
H1: "background-color: #f3f4f6; font-weight: bold;",
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
loadJSpreadsheet()
|
|
|
|
return () => {
|
|
if (jspreadsheetInstance.current) {
|
|
jspreadsheetInstance.current.destroy()
|
|
}
|
|
}
|
|
}, [teamId])
|
|
|
|
const handleSave = () => {
|
|
if (jspreadsheetInstance.current) {
|
|
const data = jspreadsheetInstance.current.getData()
|
|
console.log("Saving data:", data)
|
|
// Here you would typically save to your backend
|
|
alert("Schedule saved successfully!")
|
|
}
|
|
}
|
|
|
|
const handleExport = () => {
|
|
if (jspreadsheetInstance.current) {
|
|
jspreadsheetInstance.current.download()
|
|
}
|
|
}
|
|
|
|
const handleAddEmployee = () => {
|
|
if (jspreadsheetInstance.current) {
|
|
jspreadsheetInstance.current.insertRow()
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Card className="w-full">
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<CardTitle className="flex items-center gap-2">{teamName} - Weekly Schedule</CardTitle>
|
|
<CardDescription>Manage work shifts and schedules for your team members</CardDescription>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button onClick={handleAddEmployee} variant="outline" size="sm">
|
|
<Plus className="size-4 mr-2" />
|
|
Add Employee
|
|
</Button>
|
|
<Button onClick={handleSave} variant="outline" size="sm">
|
|
<Save className="size-4 mr-2" />
|
|
Save
|
|
</Button>
|
|
<Button onClick={handleExport} variant="outline" size="sm">
|
|
<Download className="size-4 mr-2" />
|
|
Export
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="border rounded-lg overflow-hidden">
|
|
<div ref={spreadsheetRef} className="w-full" />
|
|
</div>
|
|
<div className="mt-4 text-sm text-muted-foreground">
|
|
<p>
|
|
<strong>Instructions:</strong>
|
|
</p>
|
|
<ul className="list-disc list-inside mt-2 space-y-1">
|
|
<li>Click on any cell to edit shift times (e.g., "08:00-16:00")</li>
|
|
<li>Use "OFF" for days off</li>
|
|
<li>Right-click for context menu options</li>
|
|
<li>Use the "Add Employee" button to add new team members</li>
|
|
</ul>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|