Complete timeshift schedule management application
- 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>
This commit is contained in:
172
timeshift/v0_extracted/components/timeshift-spreadsheet.tsx
Normal file
172
timeshift/v0_extracted/components/timeshift-spreadsheet.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
"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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user