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