calendar delete and bug fixes

This commit is contained in:
2025-02-18 09:26:00 +01:00
parent 308bd35ce8
commit 2c0cd3c42d
4 changed files with 143 additions and 75 deletions
+77 -43
View File
@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useCalendarApp, ScheduleXCalendar } from '@schedule-x/react'; import { useCalendarApp, ScheduleXCalendar } from '@schedule-x/react';
import { createViewWeek } from '@schedule-x/calendar'; import { CalendarEventExternal, createViewWeek } from '@schedule-x/calendar';
import { createEventsServicePlugin } from '@schedule-x/events-service'; import { createEventsServicePlugin } from '@schedule-x/events-service';
import { createDragAndDropPlugin } from '@schedule-x/drag-and-drop'; import { createDragAndDropPlugin } from '@schedule-x/drag-and-drop';
@@ -15,74 +15,85 @@ import {
ContextMenuTrigger, ContextMenuTrigger,
} from '@/components/ui/context-menu'; } from '@/components/ui/context-menu';
import { PlusCircle } from 'lucide-react'; import { PlusCircle, Trash } from 'lucide-react';
import { parse } from 'date-fns'; import { format, parse } from 'date-fns';
import PostDialog, { today } from '@/components/postDialog'; import PostDialog, { today } from '@/components/postDialog';
function closestChild(children: any, clientX: number, clientY: number) {
let closestElement = null;
let closestDistance = Infinity;
for (let child of children) {
const childRect = child.getBoundingClientRect();
const dx = Math.abs(clientX - (childRect.left + childRect.width / 2));
const dy = Math.abs(clientY - (childRect.top + childRect.height / 2));
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestElement = child;
}
}
return closestElement;
}
function CalendarApp() { function CalendarApp() {
const eventsService = useState(() => createEventsServicePlugin())[0]; const eventsService = useState(() => createEventsServicePlugin())[0];
const dragAndDrop = useState(() => createDragAndDropPlugin())[0]; const dragAndDrop = useState(() => createDragAndDropPlugin())[0];
const [eventCount, setEventCount] = useState(0);
const calendar = useCalendarApp({ const calendar = useCalendarApp({
views: [createViewWeek()], views: [createViewWeek()],
events: [ events: [],
{
id: '1',
title: 'Linus tech tips drama',
start: '2025-02-16 01:00',
end: '2025-02-16 02:00',
},
],
plugins: [eventsService, dragAndDrop], plugins: [eventsService, dragAndDrop],
isDark: true, isDark: true,
}); });
useEffect(() => {}, [eventsService]);
const [newPostDialogOpen, setNewPostDialogOpen] = useState(false); const [newPostDialogOpen, setNewPostDialogOpen] = useState(false);
const [date, setDate] = useState<Date>(today); const [date, setDate] = useState<Date>(today);
const [selected, setSelected] = useState<string | null | undefined>(null);
const handleDialog: MouseEventHandler<HTMLSpanElement> = (event) => { const handleDialog: MouseEventHandler<HTMLSpanElement> = (event) => {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
let hourElement = null; if (target.className === 'sx__time-grid-event-time') {
let closestDistance = Infinity; setSelected(
const children = target.parentElement?.children[0].children || []; target.parentElement?.parentElement?.getAttribute(
'data-event-id'
for (let child of children) { )
const childRect = child.getBoundingClientRect();
const dx = Math.abs(
event.clientX - (childRect.left + childRect.width / 2)
); );
const dy = Math.abs( return;
event.clientY - (childRect.top + childRect.height / 2) } else {
); setSelected(null);
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
hourElement = child;
}
} }
const dayElement = target.closest('.sx__time-grid-day'); try {
const dayElement = target.closest('.sx__time-grid-day');
const dayText = dayElement const dayText = dayElement
? dayElement.getAttribute('aria-label') ? dayElement.getAttribute('aria-label')
: null; : null;
if (!dayText || !hourElement) { const hourElement = closestChild(
event.preventDefault(); target.parentElement?.children[0].children || [],
} else { event.clientX,
setDate( event.clientY
parse( );
if (!dayText || !hourElement) {
event.preventDefault();
} else {
const toDate = parse(
(dayText || '') + ' ' + (hourElement?.textContent || ''), (dayText || '') + ' ' + (hourElement?.textContent || ''),
'MMMM dd, yyyy h a', 'MMMM dd, yyyy h a',
new Date() new Date()
) );
); if (toDate instanceof Date && !isNaN(toDate.getTime()))
} setDate(toDate);
}
} catch {}
}; };
return ( return (
@@ -93,6 +104,11 @@ function CalendarApp() {
setDate={setDate} setDate={setDate}
newPostDialogOpen={newPostDialogOpen} newPostDialogOpen={newPostDialogOpen}
setNewPostDialogOpen={setNewPostDialogOpen} setNewPostDialogOpen={setNewPostDialogOpen}
setEvents={(e) => {
calendar.events.add(e);
setEventCount(eventCount + 1);
}}
eventId={eventCount}
/> />
<ContextMenu> <ContextMenu>
<ContextMenuTrigger onContextMenu={handleDialog}> <ContextMenuTrigger onContextMenu={handleDialog}>
@@ -108,8 +124,26 @@ function CalendarApp() {
}} }}
> >
<PlusCircle className="w-4 mt-0.5" />{' '} <PlusCircle className="w-4 mt-0.5" />{' '}
<p className="ml-1">New post</p>x <p className="ml-1">New post</p>
</ContextMenuItem> </ContextMenuItem>
{selected ? (
<ContextMenuItem
onClick={() => {
if (selected) {
calendar.events.remove(
parseInt(selected)
);
}
}}
className=""
>
<Trash className="w-4 mt-0.5 text-red-500" />{' '}
<p className="ml-1 text-red-500">Delete</p>
</ContextMenuItem>
) : (
''
)}
</ContextMenuContent> </ContextMenuContent>
</ContextMenu> </ContextMenu>
</div> </div>
+35 -24
View File
@@ -1,8 +1,9 @@
'use client'; 'use client';
import * as React from 'react'; import * as React from 'react';
import { Menu, ChevronDown, ChevronRight } from 'lucide-react'; import { Menu } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { usePathname } from 'next/navigation';
import { import {
Sheet, Sheet,
SheetContent, SheetContent,
@@ -10,12 +11,6 @@ import {
SheetTitle, SheetTitle,
SheetTrigger, SheetTrigger,
} from '@/components/ui/sheet'; } from '@/components/ui/sheet';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import { cn } from '@/lib/utils';
import { import {
SidebarContent, SidebarContent,
@@ -57,6 +52,7 @@ const footer = [
export function HamburgerMenu() { export function HamburgerMenu() {
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const pathname = usePathname();
return ( return (
<Sheet open={open} onOpenChange={setOpen}> <Sheet open={open} onOpenChange={setOpen}>
@@ -71,7 +67,11 @@ export function HamburgerMenu() {
{items.map((item, i) => ( {items.map((item, i) => (
<div key={i} className="w-16 mx-auto"> <div key={i} className="w-16 mx-auto">
<Link href={item.url}> <Link href={item.url}>
<item.icon className="!size-full p-4 hover:bg-foreground hover:text-background rounded-2xl" /> {pathname === item.url ? (
<item.icon className="!size-full p-2 bg-foreground text-background rounded-2xl" />
) : (
<item.icon className="!size-full p-2 hover:bg-foreground hover:text-background rounded-2xl" />
)}
</Link> </Link>
</div> </div>
))} ))}
@@ -81,7 +81,11 @@ export function HamburgerMenu() {
<SheetFooter className="mt-auto"> <SheetFooter className="mt-auto">
{footer.map((item, i) => ( {footer.map((item, i) => (
<Link href={item.url} key={i}> <Link href={item.url} key={i}>
<item.icon className="!size-full p-4 hover:bg-foreground hover:text-background rounded-2xl" /> {pathname === item.url ? (
<item.icon className="!size-full p-2 bg-foreground text-background rounded-2xl" />
) : (
<item.icon className="!size-full p-2 hover:bg-foreground hover:text-background rounded-2xl" />
)}
</Link> </Link>
))} ))}
</SheetFooter> </SheetFooter>
@@ -92,6 +96,7 @@ export function HamburgerMenu() {
export default function App({ children }: { children?: any }) { export default function App({ children }: { children?: any }) {
const isSmall = useSmallScreen(); const isSmall = useSmallScreen();
const pathname = usePathname();
return isSmall ? ( return isSmall ? (
<div className="flex flex-col"> <div className="flex flex-col">
@@ -100,7 +105,11 @@ export default function App({ children }: { children?: any }) {
{items.map((item, i) => ( {items.map((item, i) => (
<div key={i} className="w-12 mx-auto"> <div key={i} className="w-12 mx-auto">
<Link href={item.url}> <Link href={item.url}>
<item.icon className="!size-full p-2 hover:bg-foreground hover:text-background rounded-2xl" /> {pathname === item.url ? (
<item.icon className="!size-full p-2 bg-foreground text-background rounded-2xl" />
) : (
<item.icon className="!size-full p-2 hover:bg-foreground hover:text-background rounded-2xl" />
)}
</Link> </Link>
</div> </div>
))} ))}
@@ -110,28 +119,22 @@ export default function App({ children }: { children?: any }) {
{footer.map((item, i) => ( {footer.map((item, i) => (
<div key={i} className="w-12 mx-auto"> <div key={i} className="w-12 mx-auto">
<Link href={item.url}> <Link href={item.url}>
<item.icon className="!size-full p-2 hover:bg-foreground hover:text-background rounded-2xl" /> {pathname === item.url ? (
<item.icon className="!size-full p-2 bg-foreground text-background rounded-2xl" />
) : (
<item.icon className="!size-full p-2 hover:bg-foreground hover:text-background rounded-2xl" />
)}
</Link> </Link>
</div> </div>
))} ))}
</div> </div>
{/* {items.map((item, i) => (
<div key={i} className="w-12 mx-auto">
<Link href={item.url}>
<item.icon className="!size-full p-2 hover:bg-foreground hover:text-background rounded-2xl" />
</Link>
</div>
))} */}
</div> </div>
{children} {children}
</div> </div>
) : ( ) : (
<div className="flex w-full"> <div className="flex w-full">
<div className="md:w-28"> <div className="md:w-28">
<SidebarProvider <SidebarProvider open={true} defaultOpen={true}>
open={true}
defaultOpen={true}
>
<Sidebar className="w-28"> <Sidebar className="w-28">
<SidebarContent> <SidebarContent>
<SidebarGroup> <SidebarGroup>
@@ -146,7 +149,11 @@ export default function App({ children }: { children?: any }) {
className="w-16 mx-auto" className="w-16 mx-auto"
> >
<Link href={item.url}> <Link href={item.url}>
<item.icon className="!size-full p-4 hover:bg-foreground hover:text-background rounded-2xl" /> {pathname === item.url ? (
<item.icon className="!size-full p-4 bg-foreground text-background rounded-2xl" />
) : (
<item.icon className="!size-full p-4 hover:bg-foreground hover:text-background rounded-2xl" />
)}
</Link> </Link>
</SidebarMenuItem> </SidebarMenuItem>
))} ))}
@@ -162,7 +169,11 @@ export default function App({ children }: { children?: any }) {
className="w-16 mx-auto" className="w-16 mx-auto"
> >
<Link href={item.url}> <Link href={item.url}>
<item.icon className="!size-full p-4 hover:bg-foreground hover:text-background rounded-2xl" /> {pathname === item.url ? (
<item.icon className="!size-full p-4 bg-foreground text-background rounded-2xl" />
) : (
<item.icon className="!size-full p-4 hover:bg-foreground hover:text-background rounded-2xl" />
)}
</Link> </Link>
</SidebarMenuItem> </SidebarMenuItem>
))} ))}
+27 -3
View File
@@ -1,6 +1,7 @@
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription,
DialogFooter, DialogFooter,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
@@ -17,12 +18,15 @@ import { format } from 'date-fns';
import { CalendarIcon } from 'lucide-react'; import { CalendarIcon } from 'lucide-react';
import { Calendar } from './ui/calendar'; import { Calendar } from './ui/calendar';
import TimeSelect from './timeSelect'; import TimeSelect from './timeSelect';
import { CalendarEventExternal } from '@schedule-x/calendar';
type PostDialogProps = { type PostDialogProps = {
newPostDialogOpen: boolean; newPostDialogOpen: boolean;
setNewPostDialogOpen: (b: boolean) => void; setNewPostDialogOpen: (b: boolean) => void;
date: Date; date: Date;
setDate: (d: Date) => void; setDate: (d: Date) => void;
eventId: number;
setEvents: (e: CalendarEventExternal) => void;
}; };
export const today = new Date(); export const today = new Date();
@@ -33,12 +37,15 @@ export default function PostDialog({
setNewPostDialogOpen, setNewPostDialogOpen,
date, date,
setDate, setDate,
setEvents,
eventId,
}: PostDialogProps) { }: PostDialogProps) {
return ( return (
<Dialog open={newPostDialogOpen} onOpenChange={setNewPostDialogOpen}> <Dialog open={newPostDialogOpen} onOpenChange={setNewPostDialogOpen}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Feature not available</DialogTitle> <DialogTitle>New post</DialogTitle>
<DialogDescription>Schedule posts on multiple accounts</DialogDescription>
</DialogHeader> </DialogHeader>
<div className="flex flex-row gap-4"> <div className="flex flex-row gap-4">
<Popover> <Popover>
@@ -63,7 +70,7 @@ export default function PostDialog({
selected={date} selected={date}
onSelect={(d: Date | undefined) => { onSelect={(d: Date | undefined) => {
if (d) { if (d) {
const newDate = new Date(d); // Clone d const newDate = new Date(d);
newDate.setHours( newDate.setHours(
date.getHours(), date.getHours(),
date.getMinutes(), date.getMinutes(),
@@ -93,7 +100,24 @@ export default function PostDialog({
> >
Cancel Cancel
</Button> </Button>
<Button>Submit</Button> <Button
onClick={() => {
const end = new Date(date);
end.setHours(date.getHours() + 1);
setEvents({
title: 'My title',
id: eventId,
start: format(date, 'y-MM-dd hh:mm'),
end: format(end, 'y-MM-dd hh:mm'),
description: '',
});
setNewPostDialogOpen(false);
}}
>
Schedule post
</Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
+3 -4
View File
@@ -23,19 +23,18 @@ const TimeSelect: React.FC<TimeSelectProps> = ({
setDate, setDate,
className, className,
}) => { }) => {
// Generate time options for all hours (00-23) with minutes (15, 30, 45)
const times = Array.from({ length: 24 }, (_, h) => const times = Array.from({ length: 24 }, (_, h) =>
[15, 30, 45].map((m) => `${h.toString().padStart(2, '0')}:${m}`) [0, 15, 30, 45].map((m) => `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`)
).flat(); ).flat();
const currentHour = date.getHours().toString().padStart(2, '0'); const currentHour = date.getHours().toString().padStart(2, '0');
const currentMinute = date.getMinutes(); const currentMinute = date.getMinutes();
const nearestMinute = [15, 30, 45].reduce((prev, curr) => const nearestMinute = [0, 15, 30, 45].reduce((prev, curr) =>
Math.abs(curr - currentMinute) < Math.abs(prev - currentMinute) Math.abs(curr - currentMinute) < Math.abs(prev - currentMinute)
? curr ? curr
: prev : prev
); );
const selectedTime = `${currentHour}:${nearestMinute}`; const selectedTime = `${currentHour}:${nearestMinute.toString().padStart(2, '0')}`;
const handleTimeChange = (newTime: string) => { const handleTimeChange = (newTime: string) => {
const [hour, minute] = newTime.split(':').map(Number); const [hour, minute] = newTime.split(':').map(Number);