diff --git a/.cspell.json b/.cspell.json index 56963e340..91488e9ca 100644 --- a/.cspell.json +++ b/.cspell.json @@ -21,6 +21,7 @@ "APPSTORE", "arrowleft", "asel", + "alldays", "Authentificate", "authjs", "barcodes", @@ -74,6 +75,7 @@ "Darkmode", "datas", "dataToDisplay", + "daygrid", "dearmor", "deepscan", "Defaul", @@ -117,6 +119,7 @@ "Filder", "filtmembers", "firstname", + "fullcalendar", "flaticon", "fomated", "Formated", @@ -325,6 +328,7 @@ "Transpiles", "tsbuildinfo", "typeof", + "timegrid", "uicolors", "uidotdev", "UIUX", diff --git a/apps/web/app/[locale]/calendar/page.tsx b/apps/web/app/[locale]/calendar/page.tsx new file mode 100644 index 000000000..bb0bd78e5 --- /dev/null +++ b/apps/web/app/[locale]/calendar/page.tsx @@ -0,0 +1,84 @@ +"use client" +import { useOrganizationTeams } from '@app/hooks'; +import { fullWidthState } from '@app/stores/fullWidth'; +import { clsxm } from '@app/utils'; +import HeaderTabs from '@components/pages/main/header-tabs'; +import { PeoplesIcon } from 'assets/svg'; +import { withAuthentication } from 'lib/app/authenticator'; +import { Breadcrumb, Button, Container, Divider } from 'lib/components'; +import { SetupFullCalendar } from 'lib/features' +import { Footer, MainLayout } from 'lib/layout'; +import { useTranslations } from 'next-intl'; +import { useParams } from 'next/navigation'; +import React, { useMemo } from 'react' +import { useRecoilValue } from 'recoil'; + +const CalendarPage = () => { + const t = useTranslations(); + const fullWidth = useRecoilValue(fullWidthState); + const { activeTeam, isTrackingEnabled } = useOrganizationTeams(); + const params = useParams<{ locale: string }>(); + const currentLocale = params ? params.locale : null; + const breadcrumbPath = useMemo( + () => [ + { title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' }, + { title: activeTeam?.name || '', href: '/' }, + { title: "CALENDAR", href: `/${currentLocale}/calendar` } + ], + [activeTeam?.name, currentLocale, t] + ); + return ( + <> + +
+
+ +
+
+ + +
+
+ +
+
+
+

+ CALENDAR +

+
+ +
+ +
+ {/*
*/} +
+
+
+ +
+
+
+ +
+ + ) +} + +export default withAuthentication(CalendarPage, { displayName: 'Calender' }); diff --git a/apps/web/app/[locale]/kanban/page.tsx b/apps/web/app/[locale]/kanban/page.tsx index 646b39716..490d1b61f 100644 --- a/apps/web/app/[locale]/kanban/page.tsx +++ b/apps/web/app/[locale]/kanban/page.tsx @@ -166,11 +166,10 @@ const Kanban = () => {
setActiveTab(tab.value)} - className={`cursor-pointer pt-2.5 px-5 pb-[30px] text-base font-semibold ${ - activeTab === tab.value + className={`cursor-pointer pt-2.5 px-5 pb-[30px] text-base font-semibold ${activeTab === tab.value ? 'border-b-[#3826A6] text-[#3826A6] dark:text-white dark:border-b-white' : 'border-b-white dark:border-b-[#191A20] dark:text-white text-[#282048]' - }`} + }`} style={{ borderBottomWidth: '3px', borderBottomStyle: 'solid' diff --git a/apps/web/app/api/livekit/route.ts b/apps/web/app/api/livekit/route.ts index bd485b8fd..8e07b29b9 100644 --- a/apps/web/app/api/livekit/route.ts +++ b/apps/web/app/api/livekit/route.ts @@ -32,8 +32,21 @@ export async function GET(req: NextRequest) { } try { - const at = new AccessToken(apiKey, apiSecret, { identity: username }); - at.addGrant({ room, roomJoin: true, canPublish: true, canSubscribe: true, roomRecord: true }); + const at = new AccessToken(apiKey, apiSecret, { identity: username, ttl: '1h' }); + at.addGrant({ + room, + roomJoin: true, + canPublish: true, + canSubscribe: true, + roomRecord: true, + roomCreate: true, + roomAdmin: true, + recorder: true, + roomList: true, + canUpdateOwnMetadata: true, + agent: true, + canPublishData: true, + }); const token = await at.toJwt(); return NextResponse.json({ token: token }); } catch (error) { diff --git a/apps/web/lib/features/index.ts b/apps/web/lib/features/index.ts index 688b0f985..81fec7bd4 100644 --- a/apps/web/lib/features/index.ts +++ b/apps/web/lib/features/index.ts @@ -36,3 +36,6 @@ export * from './user-profile-tasks'; export * from './languages/language-item'; export * from './timezones/timezone-item'; export * from './position/position-item'; + + +export * from './integrations/calendar/setup-full-calendar' diff --git a/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx b/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx new file mode 100644 index 000000000..ffa421a35 --- /dev/null +++ b/apps/web/lib/features/integrations/calendar/setup-full-calendar.tsx @@ -0,0 +1,291 @@ +"use client" +import React, { useState, useRef } from 'react'; +import { LuCalendarPlus } from "react-icons/lu"; +import { IoIosArrowDown, IoIosArrowForward } from "react-icons/io"; +import { IoTimeSharp } from "react-icons/io5"; +import { MdTimer } from "react-icons/md"; +import FullCalendar from '@fullcalendar/react'; +import interactionPlugin from '@fullcalendar/interaction'; +import dayGridPlugin from '@fullcalendar/daygrid'; +import timeGridPlugin from '@fullcalendar/timegrid'; +import listPlugin from '@fullcalendar/list'; +import { startOfYear, endOfYear, format } from 'date-fns'; +import Image from 'next/image'; +import { Button } from 'lib/components'; +import { SettingFilterIcon } from 'assets/svg'; +import { YearDateFilter } from './year-picker-filter'; +import { cn } from 'lib/utils'; +// import { IOrganizationTeamList } from '@app/interfaces'; + +interface Event { + id?: string; + title: string; + start: string; + times?: string, + color: string; + textColor?: string, + padding?: number, + extendedProps?: { + icon?: JSX.Element; + }, + +} + +export function SetupFullCalendar() { + const [isDialogOpen, setIsDialogOpen] = useState(false); + // const [newEventTitle, setNewEventTitle] = useState(''); + const calendarRef = useRef(null); + const [selectedDate, setSelectedDate] = useState(''); + const [events, setEvents] = useState([ + { + id: '10', + title: 'Auto', + start: '2024-08-01', + color: '#dcfce7', + textColor: "#16a34a", + extendedProps: { + icon: , + }, + + + }, + { + id: '13', + title: 'Manual', + start: '2024-08-01', + color: '#ffedd5', + textColor: "#f97316", + extendedProps: { + icon: , + }, + }, + { + id: '12', + title: 'Auto', + start: '2024-08-01', + color: '#dcfce7', + textColor: "#16a34a", + extendedProps: { + icon: , + }, + + }, + { + id: '11', + title: 'Manual', + start: '2024-08-02', + color: '#ffedd5', + textColor: "#f97316", + extendedProps: { + icon: , + }, + }, + ]); + + const handleDateClick = (info: { dateStr: string }) => { + setSelectedDate(info?.dateStr); + setIsDialogOpen((prev) => !prev); + }; + + const renderEventContent = (eventInfo: any) => { + return ( +
+
+ {eventInfo.event.extendedProps.icon} + {eventInfo.event.title} +
+ 05:30h +
+ ); + }; + + const dayCellClassNames = (arg: any) => { + const today = format(new Date(), 'yyyy-MM-dd'); + const dateStr = format(arg.date, 'yyyy-MM-dd'); + if (today === dateStr) { + return ['today-cell']; + } + return ['alldays-cell']; + }; + + const handleEventClick = (info: { event: { id: string; startStr: string } }) => { + const isDelete = confirm(`Do you want to delete the event: ${info.event?.id}?`); + if (isDelete) { + const updatedEvents = events.filter(event => + event.id !== info.event.id || event.start !== info.event.startStr + ); + setEvents(updatedEvents); + } + }; + + const handleEventDrop = (info: { event: { id: string; startStr: string } }) => { + const updatedEvents = events.map(event => + event.id === info.event.id ? { ...event, start: info.event.startStr } : event + ); + setEvents(updatedEvents); + }; + + + + + + + return ( +
+
+
+
+ + +
+
+ +
+
+ { + const start = startOfYear(currentDate); + const end = endOfYear(currentDate); + return { start, end }; + }, + titleFormat: { year: 'numeric' }, + eventClassNames: (info) => info.event.classNames, + }, + }} + // weekends={false} + dayCellClassNames={dayCellClassNames} + initialView="dayGridMonth" + events={events} + dateClick={handleDateClick} + eventClick={handleEventClick} + eventDrop={handleEventDrop} + eventContent={renderEventContent} + editable={true} + + /> + +
+ {isDialogOpen && ( +
+ +
+ )} +
+ ) +} + + + +export const CardItems = ({ selectedDate }: { selectedDate: Date }) => { + return ( +
+ + {format(selectedDate, 'PPP')} + +
+ + + + + + + +
+
+ ) +} + + +export const CardItemsMember = ({ imageUrl, name, time }: { imageUrl?: string, name?: string, time?: string }) => { + return ( +
+
+ +
+ {name} + {time} +
+ +
+
+ ) +} + +export const CardItemsProjects = ({ logo, title, totalHours }: { logo?: string, title?: string, totalHours?: string }) => { + return ( +
+
+ logos +
+ {title} + {totalHours} +
+
+ +
+ ) +} + + +export function TotalHours() { + return ( +
+
+ + Total Hours 240 +
+
+ + ) +} diff --git a/apps/web/lib/features/integrations/calendar/year-picker-filter.tsx b/apps/web/lib/features/integrations/calendar/year-picker-filter.tsx new file mode 100644 index 000000000..ef59f2fd6 --- /dev/null +++ b/apps/web/lib/features/integrations/calendar/year-picker-filter.tsx @@ -0,0 +1,59 @@ +"use client" +import * as React from "react" +import { CalendarDaysIcon as CalendarIcon } from "lucide-react" +import { MdKeyboardArrowLeft, MdKeyboardArrowRight } from "react-icons/md"; +import FullCalendar from "@fullcalendar/react"; +import moment from "moment"; +interface IYearDateFilter { + calendarRef: React.MutableRefObject +} +export function YearDateFilter({ calendarRef }: IYearDateFilter) { + const current = calendarRef.current; + const [currentDate, setCurrentDate] = React.useState(new Date()); + + + const updateCurrentDate = () => { + if (calendarRef.current) { + const calendarApi = calendarRef.current.getApi(); + setCurrentDate(calendarApi.getDate()); + + } + }; + + function goNext() { + if (current) { + const calendarApi = current.getApi() + calendarApi.next() + updateCurrentDate(); + } + } + function goPrev() { + if (current) { + const calendarApi = current.getApi() + calendarApi.prev(); + updateCurrentDate(); + } + } + + React.useEffect(() => { + updateCurrentDate(); + }, [updateCurrentDate]); // deepscan-disable-line + + return ( +
+
+ + {moment(currentDate).format('MMM')}{" "}{currentDate.getFullYear()} +
+
+ + +
+
+ + ) +} diff --git a/apps/web/package.json b/apps/web/package.json index 45189cfb7..3607108c5 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -29,6 +29,12 @@ "@emoji-mart/data": "^1.1.2", "@emoji-mart/react": "^1.1.1", "@excalidraw/excalidraw": "^0.15.3", + "@fullcalendar/core": "^6.1.15", + "@fullcalendar/daygrid": "^6.1.15", + "@fullcalendar/interaction": "^6.1.15", + "@fullcalendar/list": "^6.1.15", + "@fullcalendar/react": "^6.1.15", + "@fullcalendar/timegrid": "^6.1.15", "@headlessui/react": "^1.7.7", "@heroicons/react": "^2.0.12", "@jitsi/react-sdk": "^1.3.0", diff --git a/yarn.lock b/yarn.lock index 5c2486c87..a0981a975 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2616,6 +2616,40 @@ dependencies: tslib "^2.4.0" +"@fullcalendar/core@^6.1.15": + version "6.1.15" + resolved "https://registry.yarnpkg.com/@fullcalendar/core/-/core-6.1.15.tgz#6c3f5259fc4589870228853072131219bb533f6e" + integrity sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q== + dependencies: + preact "~10.12.1" + +"@fullcalendar/daygrid@^6.1.15", "@fullcalendar/daygrid@~6.1.15": + version "6.1.15" + resolved "https://registry.yarnpkg.com/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz#91208b0955ba805ddad285a53ee6f53855146963" + integrity sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA== + +"@fullcalendar/interaction@^6.1.15": + version "6.1.15" + resolved "https://registry.yarnpkg.com/@fullcalendar/interaction/-/interaction-6.1.15.tgz#1c685d5c269388d4877b75ab2185e97d7c386cc7" + integrity sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ== + +"@fullcalendar/list@^6.1.15": + version "6.1.15" + resolved "https://registry.yarnpkg.com/@fullcalendar/list/-/list-6.1.15.tgz#d9b7ff0a50d7efa0d31234ed6caea06db6090c29" + integrity sha512-U1bce04tYDwkFnuVImJSy2XalYIIQr6YusOWRPM/5ivHcJh67Gm8CIMSWpi3KdRSNKFkqBxLPkfZGBMaOcJYug== + +"@fullcalendar/react@^6.1.15": + version "6.1.15" + resolved "https://registry.yarnpkg.com/@fullcalendar/react/-/react-6.1.15.tgz#3198b4a64e256afd37c9760c8741a9af89ade894" + integrity sha512-L0b9hybS2J4e7lq6G2CD4nqriyLEqOH1tE8iI6JQjAMTVh5JicOo5Mqw+fhU5bJ7hLfMw2K3fksxX3Ul1ssw5w== + +"@fullcalendar/timegrid@^6.1.15": + version "6.1.15" + resolved "https://registry.yarnpkg.com/@fullcalendar/timegrid/-/timegrid-6.1.15.tgz#c4630b7c03c813065154c6e3981f8d51d9d692e5" + integrity sha512-61ORr3A148RtxQ2FNG7JKvacyA/TEVZ7z6I+3E9Oeu3dqTf6M928bFcpehRTIK6zIA6Yifs7BeWHgOE9dFnpbw== + dependencies: + "@fullcalendar/daygrid" "~6.1.15" + "@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -21693,6 +21727,11 @@ preact@10.11.3: resolved "https://registry.yarnpkg.com/preact/-/preact-10.11.3.tgz#8a7e4ba19d3992c488b0785afcc0f8aa13c78d19" integrity sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg== +preact@~10.12.1: + version "10.12.1" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.12.1.tgz#8f9cb5442f560e532729b7d23d42fd1161354a21" + integrity sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"