improved calendar design
This commit is contained in:
+112
-172
@@ -1,191 +1,131 @@
|
|||||||
|
export interface CalendarSchedule {
|
||||||
|
date: Date;
|
||||||
|
platforms: { user: string }[];
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
import {
|
import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
ContextMenuContent,
|
ContextMenuContent,
|
||||||
ContextMenuItem,
|
ContextMenuItem,
|
||||||
ContextMenuTrigger,
|
ContextMenuTrigger,
|
||||||
} from '@/components/ui/context-menu';
|
} from '@/components/ui/context-menu';
|
||||||
import { PlusCircle, Trash2 } from 'lucide-react';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { Pencil, PlusCircle, Trash } from 'lucide-react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
export interface CalendarSchedule {
|
const hours = Array.from({ length: 24 }, (_, i) => `${i}:00`);
|
||||||
date: Date;
|
const days = [
|
||||||
platforms: { user: string }[];
|
'Sunday',
|
||||||
|
'Monday',
|
||||||
|
'Tuesday',
|
||||||
|
'Wednesday',
|
||||||
|
'Thursday',
|
||||||
|
'Friday',
|
||||||
|
'Saturday',
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface Event {
|
||||||
|
time: Date;
|
||||||
|
title: string;
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to get the current week's dates (Monday - Sunday)
|
interface CalendarProps {
|
||||||
const getCurrentWeek = (): { label: string; date: Date }[] => {
|
events: Event[];
|
||||||
const today = new Date();
|
setDate: (date: Date) => void;
|
||||||
const dayOfWeek = today.getDay(); // 0 (Sun) - 6 (Sat)
|
setDialog: (dialog: boolean) => void;
|
||||||
const mondayOffset = dayOfWeek === 0 ? -6 : 1 - dayOfWeek; // Adjust for Monday start
|
setEvents: (events: Event[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
return Array.from({ length: 7 }, (_, i) => {
|
const Calendar: React.FC<CalendarProps> = ({
|
||||||
const date = new Date(today);
|
|
||||||
date.setDate(today.getDate() + mondayOffset + i);
|
|
||||||
return {
|
|
||||||
label: date.toLocaleDateString('en-US', { weekday: 'short' }), // 'Mon', 'Tue', etc.
|
|
||||||
date,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Time slots from 12 PM to 11 PM
|
|
||||||
const times: { label: string; hour: number }[] = Array.from(
|
|
||||||
{ length: 12 },
|
|
||||||
(_, i) => ({
|
|
||||||
label: `${(i + 12) % 12 || 12} PM`, // '12 PM', '1 PM', ..., '11 PM'
|
|
||||||
hour: i + 12,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
export default function Calendar({
|
|
||||||
setDialog,
|
|
||||||
events,
|
events,
|
||||||
setDate,
|
setDate,
|
||||||
|
setDialog,
|
||||||
setEvents,
|
setEvents,
|
||||||
}: {
|
}) => {
|
||||||
setDialog: (b: boolean) => void;
|
|
||||||
events: CalendarSchedule[];
|
|
||||||
setDate: (d: Date) => void;
|
|
||||||
setEvents: (events: CalendarSchedule[]) => void;
|
|
||||||
}) {
|
|
||||||
const weekdays = getCurrentWeek();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="grid grid-cols-[auto,repeat(7,1fr)] border text-center text-sm">
|
||||||
<div className="grid grid-cols-1 w-full h-20 mb-2">
|
<div className="border p-2 font-bold">Time</div>
|
||||||
<div className="flex gap-2">
|
{days.map((day) => (
|
||||||
{/* Time Labels */}
|
<div key={day} className="border p-2 font-bold">
|
||||||
<div className="w-20 flex flex-col gap-2">
|
{day}
|
||||||
<div className="h-24"></div>{' '}
|
|
||||||
{/* Empty space for alignment */}
|
|
||||||
<div className="grid grid-rows-12 h-full gap-2">
|
|
||||||
{times.map((time) => (
|
|
||||||
<div
|
|
||||||
key={time.hour}
|
|
||||||
className="flex pr-2 h-16 border rounded-xl"
|
|
||||||
>
|
|
||||||
<p className="size-max m-auto">
|
|
||||||
{time.label}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Calendar Grid */}
|
|
||||||
<div className="grid grid-cols-7 gap-2 flex-1">
|
|
||||||
{weekdays.map((weekday) => (
|
|
||||||
<div
|
|
||||||
key={weekday.date.toDateString()}
|
|
||||||
className="flex flex-col h-full gap-2"
|
|
||||||
>
|
|
||||||
{/* Day Header */}
|
|
||||||
<div className="border rounded-md h-24 flex">
|
|
||||||
<p className="size-max mx-auto">
|
|
||||||
{weekday.label}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Time Slots */}
|
|
||||||
<div className="grid grid-rows-12 h-full gap-2">
|
|
||||||
{times.map((time) => {
|
|
||||||
const event = events.find(
|
|
||||||
(e) =>
|
|
||||||
e.date.getFullYear() ===
|
|
||||||
weekday.date.getFullYear() &&
|
|
||||||
e.date.getMonth() ===
|
|
||||||
weekday.date.getMonth() &&
|
|
||||||
e.date.getDate() ===
|
|
||||||
weekday.date.getDate() &&
|
|
||||||
e.date.getHours() === time.hour
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ContextMenu
|
|
||||||
key={`${weekday.date.toDateString()}-${
|
|
||||||
time.hour
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<ContextMenuTrigger>
|
|
||||||
<div
|
|
||||||
className={`border rounded-xl w-full h-16 ${
|
|
||||||
event
|
|
||||||
? 'bg-primary/20'
|
|
||||||
: ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{event && (
|
|
||||||
<div className="p-2 text-sm flex items-center justify-center">
|
|
||||||
{/* Event indicator */}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</ContextMenuTrigger>
|
|
||||||
<ContextMenuContent>
|
|
||||||
<ContextMenuItem
|
|
||||||
onClick={() => {
|
|
||||||
setDialog(true);
|
|
||||||
setDate(
|
|
||||||
new Date(
|
|
||||||
weekday.date.getFullYear(),
|
|
||||||
weekday.date.getMonth(),
|
|
||||||
weekday.date.getDate(),
|
|
||||||
time.hour,
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PlusCircle className="mr-2 h-4 w-4" />
|
|
||||||
<p>New post</p>
|
|
||||||
</ContextMenuItem>
|
|
||||||
|
|
||||||
{event && (
|
|
||||||
<ContextMenuItem
|
|
||||||
onClick={() => {
|
|
||||||
axios
|
|
||||||
.delete(
|
|
||||||
`/api/posts`,
|
|
||||||
{
|
|
||||||
params: {
|
|
||||||
schedule:
|
|
||||||
event.id,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(
|
|
||||||
() => {
|
|
||||||
setEvents(
|
|
||||||
events.filter(
|
|
||||||
(
|
|
||||||
e
|
|
||||||
) =>
|
|
||||||
e.id !==
|
|
||||||
event.id
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Trash2 className="mr-2 h-4 w-4 text-red-500" />
|
|
||||||
<p className="text-red-500">
|
|
||||||
Delete post
|
|
||||||
</p>
|
|
||||||
</ContextMenuItem>
|
|
||||||
)}
|
|
||||||
</ContextMenuContent>
|
|
||||||
</ContextMenu>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
|
|
||||||
|
{hours.map((hour, rowIdx) => (
|
||||||
|
<React.Fragment key={hour}>
|
||||||
|
<div className="border p-2">{hour}</div>
|
||||||
|
|
||||||
|
{days.map((_, colIdx) => {
|
||||||
|
const event = events.find(
|
||||||
|
(e: Event) =>
|
||||||
|
e.time.getDay() === colIdx &&
|
||||||
|
e.time.getHours() === rowIdx
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!event) {
|
||||||
|
return (
|
||||||
|
<ContextMenu key={`${colIdx}-${rowIdx}`}>
|
||||||
|
<ContextMenuTrigger asChild>
|
||||||
|
<div className="relative border h-16"></div>
|
||||||
|
</ContextMenuTrigger>
|
||||||
|
<ContextMenuContent>
|
||||||
|
<ContextMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
setDate(
|
||||||
|
new Date(2024, 3, 4, rowIdx)
|
||||||
|
);
|
||||||
|
setDialog(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusCircle className="w-4 h-4 mr-1" />
|
||||||
|
New Post
|
||||||
|
</ContextMenuItem>
|
||||||
|
</ContextMenuContent>
|
||||||
|
</ContextMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu key={`${colIdx}-${rowIdx}`}>
|
||||||
|
<ContextMenuTrigger asChild>
|
||||||
|
<div className="relative border h-16">
|
||||||
|
{event && (
|
||||||
|
<div className="absolute inset-0 bg-blue-500 text-white p-1 text-xs rounded">
|
||||||
|
{event.title}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ContextMenuTrigger>
|
||||||
|
<ContextMenuContent>
|
||||||
|
<ContextMenuItem>
|
||||||
|
<Pencil className="w-4 h-4 mr-1" />
|
||||||
|
Edit
|
||||||
|
</ContextMenuItem>
|
||||||
|
<ContextMenuItem
|
||||||
|
className="text-red-600"
|
||||||
|
onClick={() => {
|
||||||
|
axios.delete(
|
||||||
|
`/api/posts?schedule=${event.id}`
|
||||||
|
);
|
||||||
|
setEvents(
|
||||||
|
events.filter(
|
||||||
|
(e) => e.id !== event.id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash className="w-4 h-4 mr-1" />
|
||||||
|
Delete
|
||||||
|
</ContextMenuItem>
|
||||||
|
</ContextMenuContent>
|
||||||
|
</ContextMenu>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Calendar;
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import { format, parse } from 'date-fns';
|
|||||||
import PostDialog, { today } from './postDialog';
|
import PostDialog, { today } from './postDialog';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
import Calendar, { CalendarSchedule } from './calendar';
|
import Calendar, { CalendarSchedule, Event } from './calendar';
|
||||||
import { DATE_FORMAT } from '@/lib/types';
|
import { DATE_FORMAT } from '@/lib/types';
|
||||||
|
|
||||||
export default function CalendarApp() {
|
export default function CalendarApp() {
|
||||||
const sessionId = Cookies.get('session_id') || '';
|
const sessionId = Cookies.get('session_id') || '';
|
||||||
const [events, setEvents] = useState<CalendarSchedule[]>([]);
|
const [events, setEvents] = useState<Event[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
axios
|
axios
|
||||||
@@ -20,10 +20,11 @@ export default function CalendarApp() {
|
|||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const v = response.data.schedules.map((e: any) => ({
|
const v: Event[] = response.data.schedules.map((e: any) => ({
|
||||||
date: parse(e.scheduled, DATE_FORMAT, new Date()),
|
time: parse(e.scheduled, DATE_FORMAT, new Date()),
|
||||||
platforms: e.platforms,
|
platforms: e.platforms,
|
||||||
id: e._id,
|
id: e._id,
|
||||||
|
title: 'Post',
|
||||||
}));
|
}));
|
||||||
setEvents(v);
|
setEvents(v);
|
||||||
})
|
})
|
||||||
@@ -43,7 +44,7 @@ export default function CalendarApp() {
|
|||||||
setDate={setDate}
|
setDate={setDate}
|
||||||
newPostDialogOpen={postDialog}
|
newPostDialogOpen={postDialog}
|
||||||
setNewPostDialogOpen={setDialog}
|
setNewPostDialogOpen={setDialog}
|
||||||
setEvents={(e) => {
|
addEvents={(e) => {
|
||||||
setEvents([...events, e]);
|
setEvents([...events, e]);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -26,14 +26,14 @@ import { Label } from '@/components/ui/label';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { DATE_FORMAT, PostRequest, ScheduleRequest } from '@/lib/types';
|
import { DATE_FORMAT, PostRequest, ScheduleRequest } from '@/lib/types';
|
||||||
import { CalendarSchedule } from './calendar';
|
import { CalendarSchedule, Event } from './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;
|
||||||
setEvents: (e: CalendarSchedule) => void;
|
addEvents: (e: Event) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const today = new Date();
|
export const today = new Date();
|
||||||
@@ -44,7 +44,7 @@ export default function PostDialog({
|
|||||||
setNewPostDialogOpen,
|
setNewPostDialogOpen,
|
||||||
date,
|
date,
|
||||||
setDate,
|
setDate,
|
||||||
setEvents: addEvent,
|
addEvents: addEvent,
|
||||||
}: PostDialogProps) {
|
}: PostDialogProps) {
|
||||||
const [content, setContent] = useState('');
|
const [content, setContent] = useState('');
|
||||||
|
|
||||||
@@ -154,8 +154,8 @@ export default function PostDialog({
|
|||||||
|
|
||||||
addEvent({
|
addEvent({
|
||||||
id: data.id,
|
id: data.id,
|
||||||
date,
|
time: date,
|
||||||
platforms: [{ user: 'andjksds' }],
|
title: 'New Post',
|
||||||
});
|
});
|
||||||
|
|
||||||
setNewPostDialogOpen(false);
|
setNewPostDialogOpen(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user