Initial Commit
This commit is contained in:
commit
1cdfc5bad5
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
.yarn/install-state.gz
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
.env
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"ci": {
|
||||||
|
"collect": {
|
||||||
|
"staticDistDir": "./public"
|
||||||
|
},
|
||||||
|
"assert": {
|
||||||
|
"assertions": {}
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"target": "temporary-public-storage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
# or
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
'use server'
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { postMessage } from "@/app/[locale]/(protected)/app/chat/utils";
|
||||||
|
|
||||||
|
|
||||||
|
export const postMessageAction = async (id: string, message: string,) => {
|
||||||
|
const response = await postMessage(id, message)
|
||||||
|
revalidatePath("/");
|
||||||
|
return response;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
'use server'
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { revalidatePath } from "next/cache";
|
||||||
|
import {signIn} from "@/lib/auth";
|
||||||
|
export const loginUser = async (data: any) => {
|
||||||
|
try {
|
||||||
|
const response = await signIn("credentials", {
|
||||||
|
email: data.email,
|
||||||
|
password: data.password,
|
||||||
|
redirect: false,
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error as string);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,243 @@
|
||||||
|
"use client";
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import FullCalendar from "@fullcalendar/react"; // must go before plugins
|
||||||
|
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||||
|
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||||
|
import interactionPlugin, { Draggable } from "@fullcalendar/interaction";
|
||||||
|
import listPlugin from "@fullcalendar/list";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import ExternalDraggingevent from "./dragging-events";
|
||||||
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
|
import { Plus } from "lucide-react";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { CalendarEvent, CalendarCategory } from "./data"
|
||||||
|
import {
|
||||||
|
EventContentArg,
|
||||||
|
} from '@fullcalendar/core'
|
||||||
|
import EventModal from "./event-modal";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
const wait = () => new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
interface CalendarViewProps {
|
||||||
|
events: CalendarEvent[];
|
||||||
|
categories: CalendarCategory[];
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const CalendarView = ({ events, categories }: CalendarViewProps) => {
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState<string[] | null>(null);
|
||||||
|
const [selectedEventDate, setSelectedEventDate] = useState<Date | null>(null);
|
||||||
|
const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(null);
|
||||||
|
const [draggableInitialized, setDraggableInitialized] = useState<boolean>(false);
|
||||||
|
const t = useTranslations("CalendarApp")
|
||||||
|
// event canvas state
|
||||||
|
const [sheetOpen, setSheetOpen] = useState<boolean>(false);
|
||||||
|
const [date, setDate] = React.useState<Date>(new Date());
|
||||||
|
|
||||||
|
const [dragEvents] = useState([
|
||||||
|
{ title: "New Event Planning", id: "101", tag: "business" },
|
||||||
|
{ title: "Meeting", id: "102", tag: "meeting" },
|
||||||
|
{ title: "Generating Reports", id: "103", tag: "holiday" },
|
||||||
|
{ title: "Create New theme", id: "104", tag: "etc" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedCategory(categories?.map((c) => c.value));
|
||||||
|
}, [events, categories]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const draggableEl = document.getElementById("external-events");
|
||||||
|
|
||||||
|
const initDraggable = () => {
|
||||||
|
if (draggableEl) {
|
||||||
|
new Draggable(draggableEl, {
|
||||||
|
itemSelector: ".fc-event",
|
||||||
|
eventData: function (eventEl) {
|
||||||
|
let title = eventEl.getAttribute("title");
|
||||||
|
let id = eventEl.getAttribute("data");
|
||||||
|
let event = dragEvents.find((e) => e.id === id);
|
||||||
|
let tag = event ? event.tag : "";
|
||||||
|
return {
|
||||||
|
title: title,
|
||||||
|
id: id,
|
||||||
|
extendedProps: {
|
||||||
|
calendar: tag,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dragEvents.length > 0) {
|
||||||
|
initDraggable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
draggableEl?.removeEventListener("mousedown", initDraggable);
|
||||||
|
};
|
||||||
|
}, [dragEvents]);
|
||||||
|
// event click
|
||||||
|
const handleEventClick = (arg: any) => {
|
||||||
|
setSelectedEventDate(null);
|
||||||
|
setSheetOpen(true);
|
||||||
|
setSelectedEvent(arg);
|
||||||
|
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
||||||
|
};
|
||||||
|
// handle close modal
|
||||||
|
const handleCloseModal = () => {
|
||||||
|
setSheetOpen(false);
|
||||||
|
setSelectedEvent(null);
|
||||||
|
setSelectedEventDate(null);
|
||||||
|
};
|
||||||
|
const handleDateClick = (arg: any) => {
|
||||||
|
setSheetOpen(true);
|
||||||
|
setSelectedEventDate(arg);
|
||||||
|
setSelectedEvent(null);
|
||||||
|
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCategorySelection = (category: string) => {
|
||||||
|
if (selectedCategory && selectedCategory.includes(category)) {
|
||||||
|
setSelectedCategory(selectedCategory.filter((c) => c !== category));
|
||||||
|
} else {
|
||||||
|
setSelectedCategory([...selectedCategory || [], category]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClassName = (arg: EventContentArg) => {
|
||||||
|
|
||||||
|
if (arg.event.extendedProps.calendar === "holiday") {
|
||||||
|
return "destructive";
|
||||||
|
}
|
||||||
|
else if (arg.event.extendedProps.calendar === "business") {
|
||||||
|
return "primary";
|
||||||
|
} else if (arg.event.extendedProps.calendar === "personal") {
|
||||||
|
return "success";
|
||||||
|
} else if (arg.event.extendedProps.calendar === "family") {
|
||||||
|
return "info";
|
||||||
|
} else if (arg.event.extendedProps.calendar === "etc") {
|
||||||
|
return "info";
|
||||||
|
} else if (arg.event.extendedProps.calendar === "meeting") {
|
||||||
|
return "warning";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "primary";
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredEvents = events?.filter((event) =>
|
||||||
|
selectedCategory?.includes(event.extendedProps.calendar)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="grid grid-cols-12 gap-6 divide-x divide-border">
|
||||||
|
<Card className="col-span-12 lg:col-span-4 2xl:col-span-3 pb-5">
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<CardHeader className="border-none mb-2 pt-5">
|
||||||
|
<Button
|
||||||
|
onClick={handleDateClick}
|
||||||
|
className="dark:bg-background dark:text-foreground"
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4 me-1" />
|
||||||
|
{t("addEvent")}
|
||||||
|
</Button>
|
||||||
|
</CardHeader>
|
||||||
|
<div className="px-3">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={date}
|
||||||
|
onSelect={(s) => {
|
||||||
|
handleDateClick(s);
|
||||||
|
}}
|
||||||
|
className="rounded-md border w-full p-0 border-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="external-events" className=" space-y-1.5 mt-6 px-4">
|
||||||
|
<p className="text-sm font-medium text-default-700 mb-3">
|
||||||
|
{t("shortDesc")}
|
||||||
|
</p>
|
||||||
|
{dragEvents.map((event) => (
|
||||||
|
<ExternalDraggingevent key={event.id} event={event} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="py-4 text-default-800 font-semibold text-xs uppercase mt-4 mb-2 px-4">
|
||||||
|
{t("filter")}
|
||||||
|
</div>
|
||||||
|
<ul className="space-y-3 px-4">
|
||||||
|
<li className=" flex gap-3">
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedCategory?.length === categories?.length}
|
||||||
|
onClick={() => {
|
||||||
|
if (selectedCategory?.length === categories?.length) {
|
||||||
|
setSelectedCategory([]);
|
||||||
|
} else {
|
||||||
|
setSelectedCategory(categories.map((c) => c.value));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label>All</Label>
|
||||||
|
</li>
|
||||||
|
{categories?.map((category) => (
|
||||||
|
<li className="flex gap-3 " key={category.value}>
|
||||||
|
<Checkbox
|
||||||
|
className={category.className}
|
||||||
|
id={category.label}
|
||||||
|
checked={selectedCategory?.includes(category.value)}
|
||||||
|
onClick={() => handleCategorySelection(category.value)}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={category.label}>{category.label}</Label>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="col-span-12 lg:col-span-8 2xl:col-span-9 pt-5">
|
||||||
|
<CardContent className="dashcode-app-calendar">
|
||||||
|
<FullCalendar
|
||||||
|
plugins={[
|
||||||
|
dayGridPlugin,
|
||||||
|
timeGridPlugin,
|
||||||
|
interactionPlugin,
|
||||||
|
listPlugin,
|
||||||
|
]}
|
||||||
|
headerToolbar={{
|
||||||
|
left: "prev,next today",
|
||||||
|
center: "title",
|
||||||
|
right: "dayGridMonth,timeGridWeek,timeGridDay,listWeek",
|
||||||
|
}}
|
||||||
|
events={filteredEvents}
|
||||||
|
editable={true}
|
||||||
|
rerenderDelay={10}
|
||||||
|
eventDurationEditable={false}
|
||||||
|
selectable={true}
|
||||||
|
selectMirror={true}
|
||||||
|
droppable={true}
|
||||||
|
dayMaxEvents={2}
|
||||||
|
weekends={true}
|
||||||
|
eventClassNames={handleClassName}
|
||||||
|
dateClick={handleDateClick}
|
||||||
|
eventClick={handleEventClick}
|
||||||
|
initialView="dayGridMonth"
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<EventModal
|
||||||
|
open={sheetOpen}
|
||||||
|
onClose={handleCloseModal}
|
||||||
|
categories={categories}
|
||||||
|
event={selectedEvent}
|
||||||
|
selectedDate={selectedEventDate}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CalendarView;
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
|
||||||
|
const date = new Date();
|
||||||
|
const prevDay = new Date().getDate() - 1;
|
||||||
|
const nextDay = new Date(new Date().getTime() + 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
const nextMonth = date.getMonth() === 11 ? new Date(date.getFullYear() + 1, 0, 1) : new Date(date.getFullYear(), date.getMonth() + 1, 1)
|
||||||
|
// prettier-ignore
|
||||||
|
const prevMonth = date.getMonth() === 11 ? new Date(date.getFullYear() - 1, 0, 1) : new Date(date.getFullYear(), date.getMonth() - 1, 1)
|
||||||
|
export const calendarEvents = [
|
||||||
|
{
|
||||||
|
id: faker.string.uuid() ,
|
||||||
|
title: "All Day Event",
|
||||||
|
start: date,
|
||||||
|
end: nextDay,
|
||||||
|
allDay: false,
|
||||||
|
//className: "warning",
|
||||||
|
extendedProps: {
|
||||||
|
calendar: "business",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Meeting With Client",
|
||||||
|
start: new Date(date.getFullYear(), date.getMonth() + 1, -11),
|
||||||
|
end: new Date(date.getFullYear(), date.getMonth() + 1, -10),
|
||||||
|
allDay: true,
|
||||||
|
//className: "success",
|
||||||
|
extendedProps: {
|
||||||
|
calendar: "personal",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Lunch",
|
||||||
|
allDay: true,
|
||||||
|
start: new Date(date.getFullYear(), date.getMonth() + 1, -9),
|
||||||
|
end: new Date(date.getFullYear(), date.getMonth() + 1, -7),
|
||||||
|
// className: "info",
|
||||||
|
extendedProps: {
|
||||||
|
calendar: "family",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Birthday Party",
|
||||||
|
start: new Date(date.getFullYear(), date.getMonth() + 1, -11),
|
||||||
|
end: new Date(date.getFullYear(), date.getMonth() + 1, -10),
|
||||||
|
allDay: true,
|
||||||
|
//className: "primary",
|
||||||
|
extendedProps: {
|
||||||
|
calendar: "meeting",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Birthday Party",
|
||||||
|
start: new Date(date.getFullYear(), date.getMonth() + 1, -13),
|
||||||
|
end: new Date(date.getFullYear(), date.getMonth() + 1, -12),
|
||||||
|
allDay: true,
|
||||||
|
// className: "danger",
|
||||||
|
extendedProps: {
|
||||||
|
calendar: "holiday",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Monthly Meeting",
|
||||||
|
start: nextMonth,
|
||||||
|
end: nextMonth,
|
||||||
|
allDay: true,
|
||||||
|
//className: "primary",
|
||||||
|
extendedProps: {
|
||||||
|
calendar: "business",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const calendarCategories = [
|
||||||
|
{
|
||||||
|
label: "Business",
|
||||||
|
value: "business",
|
||||||
|
activeClass: "ring-primary-500 bg-primary-500",
|
||||||
|
className: "group-hover:border-blue-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Personal",
|
||||||
|
value: "personal",
|
||||||
|
activeClass: "ring-success-500 bg-success-500",
|
||||||
|
className: " group-hover:border-green-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Holiday",
|
||||||
|
value: "holiday",
|
||||||
|
activeClass: "ring-danger-500 bg-danger-500",
|
||||||
|
className: " group-hover:border-red-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Family",
|
||||||
|
value: "family",
|
||||||
|
activeClass: "ring-info-500 bg-info-500",
|
||||||
|
className: " group-hover:border-cyan-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Meeting",
|
||||||
|
value: "meeting",
|
||||||
|
activeClass: "ring-warning-500 bg-warning-500",
|
||||||
|
className: " group-hover:border-yellow-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Etc",
|
||||||
|
value: "etc",
|
||||||
|
activeClass: "ring-info-500 bg-info-500",
|
||||||
|
className: " group-hover:border-cyan-500",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const categories = [
|
||||||
|
{
|
||||||
|
label: "Business",
|
||||||
|
value: "business",
|
||||||
|
className: "data-[state=checked]:bg-primary data-[state=checked]:ring-primary",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Personal",
|
||||||
|
value: "personal",
|
||||||
|
|
||||||
|
className: "data-[state=checked]:bg-success data-[state=checked]:ring-success",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Holiday",
|
||||||
|
value: "holiday",
|
||||||
|
className: "data-[state=checked]:bg-destructive data-[state=checked]:ring-destructive ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Family",
|
||||||
|
value: "family",
|
||||||
|
className: "data-[state=checked]:bg-info data-[state=checked]:ring-info ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Meeting",
|
||||||
|
value: "meeting",
|
||||||
|
className: "data-[state=checked]:bg-warning data-[state=checked]:ring-warning",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Etc",
|
||||||
|
value: "etc",
|
||||||
|
className: "data-[state=checked]:bg-info data-[state=checked]:ring-info",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export type CalendarEvent = (typeof calendarEvents)[number]
|
||||||
|
export type CalendarCategory = (typeof calendarCategories)[number]
|
||||||
|
export type Category = (typeof categories)[number]
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
const ExternalDraggingevent = ({ event }: any) => {
|
||||||
|
const { title, id, tag } = event;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
title={title}
|
||||||
|
data-id={id}
|
||||||
|
className="fc-event px-4 py-1.5 bg-default-100 dark:bg-default-300 rounded text-sm flex items-center gap-2 shadow-sm cursor-move" >
|
||||||
|
<span
|
||||||
|
className={cn("h-2 w-2 rounded-full block", {
|
||||||
|
"bg-primary": tag === "business",
|
||||||
|
"bg-warning": tag === "meeting",
|
||||||
|
"bg-destructive": tag === "holiday",
|
||||||
|
"bg-info": tag === "etc",
|
||||||
|
})}
|
||||||
|
></span>
|
||||||
|
<span className="text-sm font-medium text-default-900">{title}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExternalDraggingevent;
|
||||||
|
|
@ -0,0 +1,277 @@
|
||||||
|
"use client"
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { useForm, Controller } from "react-hook-form";
|
||||||
|
import { cn, } from "@/lib/utils";
|
||||||
|
import { format } from "date-fns"
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { Loader2, CalendarIcon } from "lucide-react";
|
||||||
|
import DeleteConfirmationDialog from "@/components/delete-confirmation-dialog";
|
||||||
|
import { CalendarCategory } from "./data";
|
||||||
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
title: z.string().min(3, { message: "Required" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const EventModal = ({ open, onClose, categories, event, selectedDate }: {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
categories: any;
|
||||||
|
event: any;
|
||||||
|
selectedDate: any
|
||||||
|
}) => {
|
||||||
|
const [startDate, setStartDate] = useState<Date>(new Date());
|
||||||
|
const [endDate, setEndDate] = useState<Date>(new Date());
|
||||||
|
const [isPending, startTransition] = React.useTransition();
|
||||||
|
const [calendarProps, setCalendarProps] = React.useState<any>(categories[0].value);
|
||||||
|
// delete modal state
|
||||||
|
const [deleteModalOpen, setDeleteModalOpen] = useState<boolean>(false);
|
||||||
|
const [eventIdToDelete, setEventIdToDelete] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
control,
|
||||||
|
reset,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
handleSubmit,
|
||||||
|
} = useForm({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
mode: "all",
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = (data: any) => {
|
||||||
|
startTransition(async () => {
|
||||||
|
if (!event) {
|
||||||
|
data.start = startDate;
|
||||||
|
data.end = endDate;
|
||||||
|
data.allDay = false;
|
||||||
|
data.extendedProps = {
|
||||||
|
calendar: calendarProps,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (event) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedDate) {
|
||||||
|
setStartDate(selectedDate.date);
|
||||||
|
setEndDate(selectedDate.date);
|
||||||
|
}
|
||||||
|
if (event) {
|
||||||
|
setStartDate(event?.event?.start);
|
||||||
|
setEndDate(event?.event?.end);
|
||||||
|
const eventCalendar = event?.event?.extendedProps?.calendar;
|
||||||
|
if (eventCalendar) {
|
||||||
|
setCalendarProps(eventCalendar);
|
||||||
|
} else {
|
||||||
|
setCalendarProps(categories[0].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setValue("title", event?.event?.title || "");
|
||||||
|
}, [event, selectedDate, open, categories, setValue]);
|
||||||
|
|
||||||
|
const onDeleteEventAction = async () => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenDeleteModal = (eventId: string) => {
|
||||||
|
setEventIdToDelete(eventId);
|
||||||
|
setDeleteModalOpen(true);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DeleteConfirmationDialog
|
||||||
|
open={deleteModalOpen}
|
||||||
|
onClose={() => setDeleteModalOpen(false)}
|
||||||
|
onConfirm={onDeleteEventAction}
|
||||||
|
defaultToast={false}
|
||||||
|
/>
|
||||||
|
<Dialog open={open} onOpenChange={onClose}>
|
||||||
|
<DialogContent
|
||||||
|
onPointerDownOutside={onClose}
|
||||||
|
>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
{event ? "Edit Event" : "Create Event"} {event?.title}
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="mt-6 h-full">
|
||||||
|
<form className="h-full" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="space-y-4 pb-5 ">
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="title">Event Name</Label>
|
||||||
|
<Input
|
||||||
|
id="title"
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter Event Name"
|
||||||
|
{...register("title")}
|
||||||
|
/>
|
||||||
|
{errors?.title?.message && (
|
||||||
|
<div className="text-destructive text-sm">
|
||||||
|
{typeof errors?.title?.message === 'string'
|
||||||
|
? errors?.title?.message
|
||||||
|
: JSON.stringify(errors?.title?.message)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="startDate">Start Date </Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="md"
|
||||||
|
className={cn(
|
||||||
|
"w-full justify-between text-left font-normal border-default-200 text-default-600 md:px-4",
|
||||||
|
!startDate && "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{startDate ? (
|
||||||
|
format(startDate, "PP")
|
||||||
|
) : (
|
||||||
|
<span>Pick a date</span>
|
||||||
|
)}
|
||||||
|
<CalendarIcon className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<Controller
|
||||||
|
name="startDate"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={startDate}
|
||||||
|
onSelect={(date) => setStartDate(date as Date)}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="endDate">End Date</Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="md"
|
||||||
|
className={cn(
|
||||||
|
"w-full justify-between text-left font-normal border-default-200 text-default-600 md:px-4",
|
||||||
|
!endDate && "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{endDate ? (
|
||||||
|
format(endDate, "PP")
|
||||||
|
) : (
|
||||||
|
<span>Pick a date</span>
|
||||||
|
)}
|
||||||
|
<CalendarIcon className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<Controller
|
||||||
|
name="endDate"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={endDate}
|
||||||
|
onSelect={(date) => setEndDate(date as Date)}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="calendarProps">Label </Label>
|
||||||
|
<Controller
|
||||||
|
name="calendarProps"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
value={calendarProps}
|
||||||
|
onValueChange={(data) => setCalendarProps(data)}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Label" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{categories.map((category: CalendarCategory) => (
|
||||||
|
<SelectItem
|
||||||
|
value={category.value}
|
||||||
|
key={category.value}
|
||||||
|
>
|
||||||
|
{category.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-2 mt-10">
|
||||||
|
<Button type="submit" disabled={isPending} className="flex-1">
|
||||||
|
{isPending ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="me-2 h-4 w-4 animate-spin" />
|
||||||
|
{event ? "Updating..." : "Adding..."}
|
||||||
|
</>
|
||||||
|
) : event ? (
|
||||||
|
"Update Event"
|
||||||
|
) : (
|
||||||
|
"Add Event"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
{event && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
color="destructive"
|
||||||
|
onClick={() => handleOpenDeleteModal(event?.event?.id)}
|
||||||
|
className="flex-1"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EventModal;
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
export const metadata = {
|
||||||
|
title: "Calender",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { getEvents, getCategories } from "./utils";
|
||||||
|
import { Category } from "./data"
|
||||||
|
import CalendarView from "./calender-view";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const CalenderPage = async () => {
|
||||||
|
const events = await getEvents();
|
||||||
|
const categories = await getCategories();
|
||||||
|
const formattedCategories = categories.map((category: Category) => ({
|
||||||
|
...category,
|
||||||
|
activeClass: "",
|
||||||
|
}));
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<CalendarView events={events} categories={formattedCategories} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CalenderPage;
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { calendarEvents, categories } from "./data";
|
||||||
|
|
||||||
|
// get events
|
||||||
|
export const getEvents = async () => {
|
||||||
|
return calendarEvents;
|
||||||
|
};
|
||||||
|
|
||||||
|
// get categories
|
||||||
|
export const getCategories = async () => {
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
"use client";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
|
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||||
|
import { Contact, ProfileUser } from "@/app/api/chat/data";
|
||||||
|
import { useChatConfig } from "@/hooks/use-chat";
|
||||||
|
|
||||||
|
const ChatHeader = ({ contact }: { contact: any }) => {
|
||||||
|
let active = true;
|
||||||
|
const isLg = useMediaQuery("(max-width: 1024px)");
|
||||||
|
|
||||||
|
const [chatConfig, setChatConfig] = useChatConfig()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="flex-1 flex gap-3 items-center">
|
||||||
|
{isLg && (
|
||||||
|
<Button size="icon" variant='ghost' color="secondary" onClick={() => setChatConfig({
|
||||||
|
...chatConfig,
|
||||||
|
isOpen: true
|
||||||
|
})}>
|
||||||
|
<Icon icon="heroicons-outline:menu-alt-1" className="w-5 h-5" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<div className="relative inline-block">
|
||||||
|
<Avatar className="border-none shadow-none bg-transparent hover:bg-transparent">
|
||||||
|
<AvatarImage src={contact?.avatar?.src} alt="" />
|
||||||
|
<AvatarFallback>{contact?.fullName?.slice(0, 2)}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<Badge
|
||||||
|
className=" h-2 w-2 p-0 ring-1 ring-border ring-offset-[1px] absolute top-2 -end-0.5"
|
||||||
|
color={active ? "success" : "secondary"}
|
||||||
|
></Badge>
|
||||||
|
</div>
|
||||||
|
<div className="hidden lg:block">
|
||||||
|
<div className="text-default-800 text-sm font-medium mb-0.5 ">
|
||||||
|
<span className="relative">{contact?.fullName}</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-default-600 text-xs font-normal">
|
||||||
|
{active ? "Active Now" : "Offline"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-none flex gap-2">
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full bg-default-100 text-default-900 hover:bg-default-100 hover:ring-0 hover:ring-transparent"
|
||||||
|
>
|
||||||
|
<Icon icon="heroicons-outline:phone" className="text-xl" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom" align="end">
|
||||||
|
<p>Start a voice call</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full bg-default-100 text-default-900 hover:bg-default-100 hover:ring-0 hover:ring-transparent"
|
||||||
|
>
|
||||||
|
<Icon icon="heroicons-outline:video-camera" className="text-xl" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom" align="end">
|
||||||
|
<p>Start a video call</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
{!isLg && (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
onClick={() => setChatConfig({ ...chatConfig, showInfo: !chatConfig.showInfo })}
|
||||||
|
type="button"
|
||||||
|
size="icon"
|
||||||
|
className={cn(
|
||||||
|
"rounded-full bg-default-100 text-primary hover:bg-default-100 hover:ring-0 hover:ring-transparent",
|
||||||
|
{
|
||||||
|
"text-default-900": !chatConfig.showInfo,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
|
||||||
|
>
|
||||||
|
<span className="text-xl ">
|
||||||
|
{chatConfig.showInfo ? (
|
||||||
|
<Icon icon="heroicons-outline:dots-vertical" className="text-xl" />
|
||||||
|
|
||||||
|
) : (
|
||||||
|
<Icon icon="heroicons-outline:dots-horizontal" className="text-xl" />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom" align="end">
|
||||||
|
<p>Conversation information</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChatHeader;
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { useChatConfig } from '@/hooks/use-chat';
|
||||||
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
|
|
||||||
|
const InfoWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [chatConfig] = useChatConfig();
|
||||||
|
if (!chatConfig.showInfo) return null
|
||||||
|
return (
|
||||||
|
<Card className='w-[285px]'>
|
||||||
|
<ScrollArea className='h-full'>
|
||||||
|
<CardContent className='p-0'> {children}</CardContent>
|
||||||
|
</ScrollArea>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InfoWrapper
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
"use client";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
import { Annoyed, SendHorizontal } from "lucide-react";
|
||||||
|
|
||||||
|
import data from "@emoji-mart/data";
|
||||||
|
import Picker from "@emoji-mart/react";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipArrow,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { postMessageAction } from "@/action/app-actions";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
|
||||||
|
const MessageFooter = () => {
|
||||||
|
const { theme: mode } = useTheme();
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
setMessage(e.target.value);
|
||||||
|
e.target.style.height = "auto"; // Reset the height to auto to adjust
|
||||||
|
e.target.style.height = `${e.target.scrollHeight - 15}px`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectEmoji = (emoji: any) => {
|
||||||
|
setMessage(message + emoji.native);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!message) return;
|
||||||
|
const data: any = {
|
||||||
|
message,
|
||||||
|
};
|
||||||
|
|
||||||
|
await postMessageAction("55fe838e-9a09-4caf-a591-559803309ef1", "sfsfsf");
|
||||||
|
setMessage("");
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="w-full flex items-end gap-1 lg:gap-4 lg:px-4 relative px-2 "
|
||||||
|
>
|
||||||
|
<div className="flex-none flex gap-1 absolute md:static top-0 left-1.5 z-10 ">
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full hover:ring-0 hover:ring-transparent bg-default-100 hover:bg-default-100 hover:text-default-900 text-default-900">
|
||||||
|
<Icon icon="heroicons-outline:link" className="w-5 h-5" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Add link</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full hover:ring-0 hover:ring-transparent bg-default-100 hover:bg-default-100 hover:text-default-900 text-default-900">
|
||||||
|
<Annoyed className="w-6 h-6 text-default" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent side="top" align="start" className="w-fit p-0 shadow-none border-none bottom-0 rtl:left-5 ltr:-left-[110px]">
|
||||||
|
<Picker
|
||||||
|
data={data}
|
||||||
|
onEmojiSelect={handleSelectEmoji}
|
||||||
|
theme={mode === "dark" ? "dark" : "light"}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="flex gap-1 relative">
|
||||||
|
<textarea
|
||||||
|
value={message}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Type your message..."
|
||||||
|
className="bg-background focus:outline-none rounded-xl break-words ps-8 md:ps-3 px-3 flex-1 h-10 pt-2 p-1 pr-8 no-scrollbar "
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter" && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSubmit(e as any);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
minHeight: "40px",
|
||||||
|
maxHeight: "120px",
|
||||||
|
overflowY: "auto",
|
||||||
|
resize: "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
type="submit"
|
||||||
|
className="rounded-full hover:ring-0 hover:ring-transparent bg-default-100 hover:bg-default-100 hover:text-default-900 text-default-900"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon="heroicons-outline:paper-airplane"
|
||||||
|
className="transform rotate-[60deg] w-5 h-5"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent align="end">
|
||||||
|
<p>Send Message</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MessageFooter;
|
||||||
|
|
@ -0,0 +1,241 @@
|
||||||
|
|
||||||
|
import { getChatsByContactId, getProfileUser } from '../utils'
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Card, CardContent, CardHeader, CardFooter } from "@/components/ui/card";
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { redirect } from '@/components/navigation';
|
||||||
|
import MessageFooter from './components/message-footer';
|
||||||
|
import ChatHeader from './components/chat-header';
|
||||||
|
import InfoWrapper from './components/info-wrapper';
|
||||||
|
import { MoreHorizontal } from 'lucide-react';
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
|
|
||||||
|
const socials = [
|
||||||
|
{
|
||||||
|
name: "facebook",
|
||||||
|
icon: "bi:facebook",
|
||||||
|
link: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "twitter",
|
||||||
|
link: "#",
|
||||||
|
icon: "bi:twitter",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "instagram",
|
||||||
|
link: "#",
|
||||||
|
icon: "bi:instagram",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const ChatPageSingle = async ({ params: { id } }: { params: { id: string }; }) => {
|
||||||
|
|
||||||
|
const { chat, contact } = await getChatsByContactId(id)
|
||||||
|
const profile = await getProfileUser()
|
||||||
|
|
||||||
|
if (!contact) {
|
||||||
|
redirect('/app/chat')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Card className="h-full flex flex-col flex-1 ">
|
||||||
|
<CardHeader className="flex-none mb-0 border-b border-default-200 py-5">
|
||||||
|
<ChatHeader contact={contact} />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className=" relative flex-1 overflow-y-auto no-scrollbar">
|
||||||
|
{chat && chat?.chat?.length > 0 ? (
|
||||||
|
chat?.chat?.map(({ senderId, message, }, index) => (
|
||||||
|
<div className="block " key={index}>
|
||||||
|
{senderId === "e2c1a571-5f7e-4f56-9020-13f98b0eaba2" ? (
|
||||||
|
<>
|
||||||
|
<div className="flex gap-2 items-start justify-end group w-full mb-4">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<div className="opacity-0 invisible group-hover:opacity-100 group-hover:visible ">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<span className="w-7 h-7 rounded-full bg-default-200 flex items-center justify-center">
|
||||||
|
<MoreHorizontal />
|
||||||
|
</span>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
className="w-fit"
|
||||||
|
align="end"
|
||||||
|
side="top"
|
||||||
|
>
|
||||||
|
<DropdownMenuItem>Remove</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Forward</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
<div className="whitespace-pre-wrap break-all">
|
||||||
|
<div className="bg-default-100 text-default-900 text-sm p-3 font-normal rounded-md ">
|
||||||
|
{message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="font-normal text-xs text-default-400 dark:text-default-600 text-start mt-1"> 2:40 pm </div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-none self-end -translate-y-5">
|
||||||
|
<div className="h-8 w-8 rounded-full ">
|
||||||
|
<Image
|
||||||
|
src={profile?.avatar}
|
||||||
|
alt=""
|
||||||
|
className="block w-full h-full object-cover rounded-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="flex gap-2 items-start group mb-4">
|
||||||
|
<div className="flex-none self-end -translate-y-5">
|
||||||
|
<div className="h-8 w-8 rounded-full">
|
||||||
|
<Image
|
||||||
|
src={contact?.avatar || `/images/users/user-5.jpg`}
|
||||||
|
alt=""
|
||||||
|
className="block w-full h-full object-cover rounded-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 flex flex-col gap-2">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<div className="whitespace-pre-wrap break-all relative z-[1]">
|
||||||
|
<div className="bg-default-100 text-default-900 text-sm p-3 font-normal rounded-md ">
|
||||||
|
{message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="opacity-0 invisible group-hover:opacity-100 group-hover:visible ">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<span className="w-7 h-7 rounded-full bg-default-200 flex items-center justify-center">
|
||||||
|
<MoreHorizontal />
|
||||||
|
</span>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
className="w-fit"
|
||||||
|
align="end"
|
||||||
|
side="top"
|
||||||
|
>
|
||||||
|
<DropdownMenuItem>Remove</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Forward</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="font-normal text-xs text-default-400 dark:text-default-600 text-start mt-1"> 2:40 pm </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="text-center absolute start-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||||
|
<Icon icon="typcn:messages" className="h-20 w-20 text-default-300 mx-auto" />
|
||||||
|
<div className="mt-4 text-lg font-medium text-default-500">No messages </div>
|
||||||
|
<div className="mt-1 text-sm font-medium text-default-400">{`don't worry, just take a deep breath & say "Hello"`}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="flex-none flex-col px-0 py-4 border-t border-border">
|
||||||
|
<MessageFooter />
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<InfoWrapper>
|
||||||
|
<h4 className="text-xl text-default-900 font-medium mb-8 px-6 mt-6">
|
||||||
|
About
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div className='flex flex-col items-center px-6'>
|
||||||
|
<Avatar className="h-24 w-24 border-none shadow-none bg-transparent hover:bg-transparent">
|
||||||
|
<AvatarImage src={contact?.avatar?.src || `/images/users/user-5.jpg`} alt="" />
|
||||||
|
<AvatarFallback>{contact?.fullName?.slice(0, 2)}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="text-center mt-4 ">
|
||||||
|
<h5 className="text-base text-default-600 font-medium mb-1">
|
||||||
|
{contact?.fullName}
|
||||||
|
</h5>
|
||||||
|
<h6 className="text-xs text-default-600 font-normal">
|
||||||
|
{contact?.role}
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="mt-5 px-6 space-y-4 border-b border-default-200 pb-5 ">
|
||||||
|
<li className="flex justify-between text-sm text-default-600 ">
|
||||||
|
<div className="flex gap-2 items-start ">
|
||||||
|
<Icon
|
||||||
|
icon="heroicons-outline:location-marker"
|
||||||
|
className="text-base"
|
||||||
|
/>
|
||||||
|
<span>Location</span>
|
||||||
|
</div>
|
||||||
|
<div className="font-medium">Bangladesh</div>
|
||||||
|
</li>
|
||||||
|
<li className="flex justify-between text-sm text-default-600 ">
|
||||||
|
<div className="flex gap-2 items-start">
|
||||||
|
<Icon icon="heroicons-outline:user" className="text-base" />
|
||||||
|
<span>Members since</span>
|
||||||
|
</div>
|
||||||
|
<div className="font-medium">Oct 2021</div>
|
||||||
|
</li>
|
||||||
|
<li className="flex justify-between text-sm text-default-600 ">
|
||||||
|
<div className="flex gap-2 items-start ">
|
||||||
|
<Icon icon="heroicons-outline:translate" className="text-base" />
|
||||||
|
<span>Language</span>
|
||||||
|
</div>
|
||||||
|
<div className="font-medium">English</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul className="mt-5 px-6 space-y-4 border-b border-default-200 pb-5 ">
|
||||||
|
{socials?.map((slink, sindex) => (
|
||||||
|
<li
|
||||||
|
key={sindex}
|
||||||
|
className="text-sm text-default-600"
|
||||||
|
>
|
||||||
|
<button className="flex gap-2">
|
||||||
|
<Icon icon={slink.icon} className="text-base" />
|
||||||
|
<span className="capitalize font-normal text-default-600">
|
||||||
|
{slink.name}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<h4 className="py-4 text-sm px-6 text-default-500 font-medium">
|
||||||
|
Shared documents
|
||||||
|
</h4>
|
||||||
|
<div className="grid grid-cols-3 gap-2 px-6">
|
||||||
|
{
|
||||||
|
["/images/chat/sd1.png", "/images/chat/sd2.png", "/images/chat/sd3.png", "/images/chat/sd4.png", "/images/chat/sd5.png", "/images/chat/sd6.png"].map((image, index) => (
|
||||||
|
<Image
|
||||||
|
key={`image-${index}`}
|
||||||
|
src={image}
|
||||||
|
alt=""
|
||||||
|
width={200}
|
||||||
|
height={100}
|
||||||
|
className='w-full h-12 object-cover rounded-md'
|
||||||
|
/>
|
||||||
|
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</InfoWrapper>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatPageSingle
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
'use client'
|
||||||
|
import { useMediaQuery } from '@/hooks/use-media-query';
|
||||||
|
import React from 'react'
|
||||||
|
import { useChatConfig } from '@/hooks/use-chat';
|
||||||
|
|
||||||
|
const ChatWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [chatConfig, setChatConfig] = useChatConfig();
|
||||||
|
const { isOpen } = chatConfig
|
||||||
|
const isTablet = useMediaQuery("(min-width: 1024px)");
|
||||||
|
return (
|
||||||
|
<div className=' app-height flex gap-5 relative'>
|
||||||
|
{!isTablet && isOpen && (
|
||||||
|
<div
|
||||||
|
onClick={() => setChatConfig({ ...chatConfig, isOpen: false })}
|
||||||
|
className="overlay bg-default-900 dark:bg-default-900 dark:bg-opacity-60 bg-opacity-60 backdrop-filter
|
||||||
|
backdrop-blur-sm absolute w-full flex-1 inset-0 z-20 rounded-md"
|
||||||
|
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatWrapper
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
"use client";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useChatConfig } from "@/hooks/use-chat";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
const Blank = () => {
|
||||||
|
const isLg = useMediaQuery("(max-width: 1024px)");
|
||||||
|
const [chatConfig, setChatConfig] = useChatConfig()
|
||||||
|
const t = useTranslations("ChatApp");
|
||||||
|
return (
|
||||||
|
<Card className="flex-1 h-full">
|
||||||
|
<CardContent className="h-full flex justify-center items-center">
|
||||||
|
<div className="text-center flex flex-col items-center">
|
||||||
|
<Icon icon="uiw:message" className="text-7xl text-default-300" />
|
||||||
|
<div className="mt-4 text-lg font-medium text-default-500">
|
||||||
|
{t("blankMessageTitle")}
|
||||||
|
</div>
|
||||||
|
<p className="mt-1 text-sm font-medium text-default-400">
|
||||||
|
{t("blankMessageDesc")}
|
||||||
|
</p>
|
||||||
|
{isLg && (
|
||||||
|
<Button className="mt-2" onClick={() => setChatConfig({ ...chatConfig, isOpen: true })}>
|
||||||
|
Start Conversation
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Blank;
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
|
||||||
|
'use client'
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
import { type Contact as ContactType, type Chat as ChatType } from "../utils";
|
||||||
|
import { Link, usePathname } from "@/components/navigation";
|
||||||
|
import { useChatConfig } from "@/hooks/use-chat";
|
||||||
|
|
||||||
|
|
||||||
|
const ContactList = ({ contact }: {
|
||||||
|
contact: ContactType,
|
||||||
|
|
||||||
|
}) => {
|
||||||
|
const { avatar, id, fullName, status, about, unreadmessage, date } =
|
||||||
|
contact;
|
||||||
|
|
||||||
|
const pathname = usePathname();
|
||||||
|
const [chatConfig, setChatConfig] = useChatConfig()
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
onClick={() => setChatConfig({
|
||||||
|
...chatConfig,
|
||||||
|
isOpen: false
|
||||||
|
})}
|
||||||
|
href={`/app/chat/${id}`} className={cn(
|
||||||
|
" gap-4 py-2 lg:py-2.5 px-3 border-l-2 border-transparent hover:bg-default-100 cursor-pointer flex ",
|
||||||
|
{
|
||||||
|
"lg:bg-default-100 ": `/app/chat/${id}` === pathname
|
||||||
|
}
|
||||||
|
)} >
|
||||||
|
|
||||||
|
|
||||||
|
<div className="flex-1 flex items-center gap-3 ">
|
||||||
|
<div className="relative inline-block ">
|
||||||
|
<Avatar className="border-none bg-transparent hover:bg-transparent">
|
||||||
|
<AvatarImage src={avatar.src} />
|
||||||
|
<AvatarFallback className="uppercase">
|
||||||
|
{fullName.slice(0, 2)}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<Badge
|
||||||
|
className=" h-2 w-2 p-0 ring-1 ring-border ring-offset-[1px] items-center justify-center absolute top-2 -end-[3px]"
|
||||||
|
color={status === "online" ? "success" : "secondary"}
|
||||||
|
></Badge>
|
||||||
|
</div>
|
||||||
|
<div className="block">
|
||||||
|
<div className="truncate max-w-[120px]">
|
||||||
|
<span className="text-sm text-default-900 font-medium">
|
||||||
|
{" "}
|
||||||
|
{fullName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="truncate max-w-[120px]">
|
||||||
|
<span className=" text-xs text-default-700 ">
|
||||||
|
{about}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-none flex-col items-end gap-2 hidden lg:flex">
|
||||||
|
<span className="text-xs text-default-600 text-end uppercase">
|
||||||
|
{date}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"h-[14px] w-[14px] flex items-center justify-center bg-default-400 rounded-full text-default-foreground text-[10px] font-medium",
|
||||||
|
{
|
||||||
|
"bg-[#FFC155]": unreadmessage > 0,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{unreadmessage === 0 ? (
|
||||||
|
<Icon icon="uil:check" className="text-sm" />
|
||||||
|
) : (
|
||||||
|
unreadmessage
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContactList;
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Icon } from "@/components/ui/icon"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { useChatConfig } from "@/hooks/use-chat";
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
|
||||||
|
const MyProfile = () => {
|
||||||
|
const [chatConfig, setChatConfig] = useChatConfig();
|
||||||
|
let status = "active";
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between gap-1">
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<div className="flex-none">
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src="/images/users/user-1.jpg" />
|
||||||
|
<AvatarFallback>SC</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 text-start">
|
||||||
|
<div className="text-default-800 text-sm font-medium mb-1">
|
||||||
|
Jane Cooper
|
||||||
|
<span className="bg-success inline-block h-2.5 w-2.5 rounded-full ms-3"></span>
|
||||||
|
</div>
|
||||||
|
<div className=" text-default-500 text-xs font-normal">
|
||||||
|
Available
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="w-8 h-8"
|
||||||
|
color="secondary"
|
||||||
|
rounded="full"
|
||||||
|
onClick={() => setChatConfig({ ...chatConfig, showProfile: true })}
|
||||||
|
>
|
||||||
|
<Icon icon="heroicons-outline:dots-horizontal" className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cn('absolute bg-card rounded-md h-full start-0 top-0 bottom-0 w-full z-50', {
|
||||||
|
'hidden -start-full': !chatConfig.showProfile
|
||||||
|
})}>
|
||||||
|
<ScrollArea className="h-full">
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button size="icon" color="secondary" className="w-8 h-8" rounded="full" onClick={() => setChatConfig({ ...chatConfig, showProfile: false })}>
|
||||||
|
<Icon icon="heroicons-outline:x" className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-3">
|
||||||
|
<div className="relative">
|
||||||
|
<Avatar className="h-16 w-16 border border-default-200 p-1 bg-transparent hover:bg-transparent ">
|
||||||
|
<AvatarImage src="/images/users/user-1.jpg" className="rounded-full" />
|
||||||
|
<AvatarFallback>SC</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<span
|
||||||
|
className={cn("absolute top-3 -end-[3px] h-3 w-3 rounded-full bg-success border border-primary-foreground",
|
||||||
|
{
|
||||||
|
"bg-success": status === "active",
|
||||||
|
"bg-warning": status === "away",
|
||||||
|
"bg-destructive": status === "busy",
|
||||||
|
"bg-secondary": status === "offline",
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-default-800 text-sm font-medium mb-0.5">
|
||||||
|
Jane Cooper
|
||||||
|
</div>
|
||||||
|
<div className=" text-default-500 text-xs font-normal">
|
||||||
|
Admin
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="my-8">
|
||||||
|
<Label htmlFor="bio" className="mb-2 block text-default-900"> About </Label>
|
||||||
|
<Textarea id="bio" placeholder="About your self" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="status" className="block mb-3 text-default-700">Status</Label>
|
||||||
|
<RadioGroup defaultValue="comfortable">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<RadioGroupItem value="default" id="active" color="success" />
|
||||||
|
<Label htmlFor="active">Active</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<RadioGroupItem value="busy" id="busy" color="destructive" />
|
||||||
|
<Label htmlFor="busy">Do Not Disturb</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<RadioGroupItem value="away" id="away" color="warning" />
|
||||||
|
<Label htmlFor="away">Away</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<RadioGroupItem value="offline" id="offline" color="warning" />
|
||||||
|
<Label htmlFor="offline">Offline</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
<Button className="mt-7">Logout</Button>
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MyProfile;
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import React from 'react';
|
||||||
|
import {Search as SearchIcon} from "lucide-react"
|
||||||
|
const Search = () => {
|
||||||
|
return (
|
||||||
|
<div className='relative'>
|
||||||
|
<SearchIcon className='absolute top-1/2 -translate-y-1/2 start-6 w-4 h-4 text-default-600' />
|
||||||
|
<Input placeholder='Search...' className='dark:bg-transparent rounded-none border-l-0 border-r-0 dark:border-default-300 focus:border-default-200 ps-12 text-lg font-normal' size="lg"/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Search;
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Dashcode Next Js",
|
||||||
|
description: "Dashcode is a popular dashboard template.",
|
||||||
|
};
|
||||||
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import ContactList from "./components/contact-list";
|
||||||
|
import { getContacts } from './utils';
|
||||||
|
import MyProfile from './components/my-profile';
|
||||||
|
import Search from './components/search';
|
||||||
|
import ChatSidebarWrapper from './sidebar-wrapper';
|
||||||
|
import ChatWrapper from './chat-wrapper';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const layout = async ({ children }: { children: React.ReactNode }) => {
|
||||||
|
|
||||||
|
const contacts = await getContacts()
|
||||||
|
return (
|
||||||
|
<ChatWrapper>
|
||||||
|
<ChatSidebarWrapper>
|
||||||
|
<Card className="h-full pb-0 ">
|
||||||
|
<CardHeader className="border-none pb-3">
|
||||||
|
<MyProfile />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="pt-0 px-0 h-full ">
|
||||||
|
<ScrollArea className="lg:h-[calc(100%-62px)] h-[calc(100%-80px)] ">
|
||||||
|
<div className='sticky top-0 z-10 bg-card'>
|
||||||
|
<Search />
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
contacts?.map((contact) => {
|
||||||
|
return (
|
||||||
|
<ContactList
|
||||||
|
key={contact.id}
|
||||||
|
contact={contact}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</ScrollArea>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</ChatSidebarWrapper>
|
||||||
|
<div className='flex-1 h-full flex gap-5'>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</ChatWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default layout
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Blank from "./components/blank-chat"
|
||||||
|
const ChatPage = async () => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Blank />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatPage
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
'use client'
|
||||||
|
import { useMediaQuery } from '@/hooks/use-media-query'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import React from 'react'
|
||||||
|
import { useChatConfig } from '@/hooks/use-chat'
|
||||||
|
const ChatSidebarWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [chatConfig] = useChatConfig()
|
||||||
|
const { isOpen } = chatConfig
|
||||||
|
const isTablet = useMediaQuery("(min-width: 1024px)");
|
||||||
|
if (!isTablet) {
|
||||||
|
return (
|
||||||
|
<div className={cn('absolute h-full start-0 w-[240px] z-50 ', {
|
||||||
|
'-start-full': !isOpen
|
||||||
|
})}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className='relative w-[320px] h-full '>{children}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatSidebarWrapper
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { chats, contacts, profileUser } from '@/app/api/chat/data'
|
||||||
|
import { baseURL } from '@/config'
|
||||||
|
|
||||||
|
|
||||||
|
export const getContacts = async () => {
|
||||||
|
return contacts
|
||||||
|
}
|
||||||
|
|
||||||
|
// get chats by contact id
|
||||||
|
export const getChatsByContactId = async (contactId: string) => {
|
||||||
|
|
||||||
|
const chat = chats.find(chat => chat.id === contactId)
|
||||||
|
const contact = contacts.find(contact => contact.id === contactId)
|
||||||
|
return {
|
||||||
|
chat,
|
||||||
|
contact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// get contact by id
|
||||||
|
export const getContactById = async (contactId: string) => {
|
||||||
|
return contacts.find(contact => contact.id === contactId)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// get profile user
|
||||||
|
export const getProfileUser = async () => {
|
||||||
|
return profileUser
|
||||||
|
}
|
||||||
|
export type Chat = typeof chats[number];
|
||||||
|
export type Contact = typeof contacts[number];
|
||||||
|
export type ProfileUser = typeof profileUser;
|
||||||
|
|
||||||
|
// post message
|
||||||
|
export const postMessage = async (id: string, data: any) => {
|
||||||
|
const res = await fetch(`${baseURL}/chat/${id}`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`Error creating task`);
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
'use client'
|
||||||
|
import {
|
||||||
|
MoveLeft,
|
||||||
|
|
||||||
|
} from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useRouter } from '@/components/navigation';
|
||||||
|
const GoBack = () => {
|
||||||
|
const router = useRouter()
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={router.back}
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full bg-default-100 hover:text-default-50 hover:outline-0 hover:outline-offset-0 hover:border-0 hover:ring-0 text-default-600 hover:ring-offset-0 p-4"
|
||||||
|
>
|
||||||
|
<MoveLeft className=" h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GoBack
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
import { Link } from '@/i18n/routing';
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardHeader, CardContent } from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Ellipsis,
|
||||||
|
LogOutIcon,
|
||||||
|
Printer,
|
||||||
|
Star,
|
||||||
|
} from "lucide-react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { getMailById } from "../utils";
|
||||||
|
import { Alert } from "@/components/ui/alert";
|
||||||
|
import GoBack from "./components/go-back"
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip"
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
const MailDetails = async ({ params: { id } }: { params: { id: string }; }) => {
|
||||||
|
const mail = await getMailById(id)
|
||||||
|
if (!mail) {
|
||||||
|
return <Alert color="destructive"> Mail not found</Alert>
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Card className=" h-full overflow-auto">
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between p-4 border-b border-solid">
|
||||||
|
<GoBack />
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full bg-default-100 hover:text-default-50 hover:outline-0 hover:outline-offset-0 hover:border-0 hover:ring-0 text-default-600 hover:ring-offset-0 p-4"
|
||||||
|
>
|
||||||
|
<LogOutIcon className=" h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Forward</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full bg-default-100 hover:text-default-50 hover:outline-0 hover:outline-offset-0 hover:border-0 hover:ring-0 text-default-600 hover:ring-offset-0 p-4"
|
||||||
|
>
|
||||||
|
<Star className=" h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Favourite</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full bg-default-100 hover:text-default-50 hover:outline-0 hover:outline-offset-0 hover:border-0 hover:ring-0 text-default-600 hover:ring-offset-0 p-4"
|
||||||
|
>
|
||||||
|
<Box className=" h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Archive</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full bg-default-100 hover:text-default-50 hover:outline-0 hover:outline-offset-0 hover:border-0 hover:ring-0 text-default-600 hover:ring-offset-0 p-4"
|
||||||
|
>
|
||||||
|
<Printer className=" h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Print</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full bg-default-100 hover:text-default-50 hover:outline-0 hover:outline-offset-0 hover:border-0 hover:ring-0 text-default-600 hover:ring-offset-0 p-4"
|
||||||
|
>
|
||||||
|
<Ellipsis className=" h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Actions</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent >
|
||||||
|
<p className="text-lg font-semibold text-default-800 mt-6">
|
||||||
|
Pay bills & win up to 600$ Cashback!
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center mt-4 gap-4">
|
||||||
|
<Avatar className=" h-8 w-8">
|
||||||
|
<AvatarImage
|
||||||
|
src="https://images.unsplash.com/photo-1531427186611-ecfd6d936c79?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80"
|
||||||
|
alt="Avatar image"
|
||||||
|
/>
|
||||||
|
<AvatarFallback>JD</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<p className="text-sm text-default-700 font-semibold">John Doe</p>
|
||||||
|
</div>
|
||||||
|
<div className="my-4 border-b border-solid border-default-200 pb-6 space-y-4 text-default-600 text-base">
|
||||||
|
<p>Hi Jane Cooper,</p>
|
||||||
|
<p>
|
||||||
|
Jornalists call this critical, introductory section the Lede, and
|
||||||
|
when bridge properly executed, it is that carries your reader from
|
||||||
|
an headine try at attention-grabbing to the body of your blog post,
|
||||||
|
if you want to get it right on of these 10 clever ways to omen your
|
||||||
|
next blog
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
posr with a bang With resrpect, i must disagree with Mr.Zinsser. We
|
||||||
|
all know the most part of important part of any article is the
|
||||||
|
title.Without a compelleing title, your reader will not even get to
|
||||||
|
the first sentence.After the title, however, the first few sentences
|
||||||
|
of your article are certainly the most important part.
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<p>Best regards,</p>
|
||||||
|
<p>Esther Howard</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="py-5 flex gap-5 flex-wrap items-center border-b border-solid border-default-200">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<Image
|
||||||
|
className="w-[150px] h-[95px]"
|
||||||
|
width={500}
|
||||||
|
height={300}
|
||||||
|
src="/images/all-img/inbox-1.png"
|
||||||
|
alt="mail"
|
||||||
|
/>
|
||||||
|
<Link className="text-primary mt-1" href="/">Download</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<Image
|
||||||
|
className="w-[150px] h-[95px]"
|
||||||
|
width={500}
|
||||||
|
height={300}
|
||||||
|
src="/images/all-img/inbox-2.png"
|
||||||
|
alt="mail"
|
||||||
|
/>
|
||||||
|
<Link className="text-primary mt-1" href="/">Download</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<Image
|
||||||
|
className="w-[150px] h-[95px]"
|
||||||
|
width={500}
|
||||||
|
height={300}
|
||||||
|
src="/images/all-img/inbox-3.png"
|
||||||
|
alt="mail"
|
||||||
|
/>
|
||||||
|
<Link className="text-primary mt-1" href="/">Download</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button color="secondary" variant="soft" size="md" className="mt-4">
|
||||||
|
<Icon icon="heroicons:chat-bubble-left-right" className="h-5 w-5 me-3" /> Reply
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MailDetails;
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
'use client'
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import Select, { MultiValue } from 'react-select'
|
||||||
|
import { Plus } from "lucide-react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { useForm, SubmitHandler, Controller } from "react-hook-form"
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
|
||||||
|
|
||||||
|
interface Option {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
image?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Inputs = {
|
||||||
|
subject: string;
|
||||||
|
description: string;
|
||||||
|
mailTo: MultiValue<Option>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mailToOptions: Option[] = [
|
||||||
|
{ value: "mahedi", label: "Mahedi Amin", image: "/images/avatar/av-1.svg" },
|
||||||
|
{ value: "sovo", label: "Sovo Haldar", image: "/images/avatar/av-2.svg" },
|
||||||
|
{ value: "rakibul", label: "Rakibul Islam", image: "/images/avatar/av-3.svg" },
|
||||||
|
{ value: "pritom", label: "Pritom Miha", image: "/images/avatar/av-4.svg" },
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const Compose = () => {
|
||||||
|
const t = useTranslations("EmailApp");
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
const [value, setValue] = useState<string>('Hello World');
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
control
|
||||||
|
} = useForm<Inputs>();
|
||||||
|
const onSubmit: SubmitHandler<Inputs> = (data) => {
|
||||||
|
setOpen(false)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
size="lg"
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
className="dark:bg-background dark:ring-background dark:text-foreground"
|
||||||
|
>
|
||||||
|
<Plus className="w-6 h-6 me-1.5" />
|
||||||
|
{t("compose")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader className="mb-6">
|
||||||
|
<DialogTitle> {t("composeEmail")}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-3.5">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="assignee">To</Label>
|
||||||
|
<Controller
|
||||||
|
name="mailTo"
|
||||||
|
control={control}
|
||||||
|
defaultValue={[]}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
className="react-select"
|
||||||
|
classNamePrefix="select"
|
||||||
|
{...field}
|
||||||
|
options={mailToOptions}
|
||||||
|
isMulti
|
||||||
|
onChange={(selectedOption) => field.onChange(selectedOption)}
|
||||||
|
getOptionLabel={(option) => (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Image width={40} height={40} src={option.image as string} alt={option.label} className="w-8 h-8 rounded-full me-2" />
|
||||||
|
<span className="text-sm font-medium">{option.label}</span>
|
||||||
|
</div>
|
||||||
|
) as unknown as string}
|
||||||
|
placeholder="Select..."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="subject">Subject</Label>
|
||||||
|
<Input
|
||||||
|
id="subject"
|
||||||
|
placeholder="Type Subject..."
|
||||||
|
{...register("subject")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="description">Description</Label>
|
||||||
|
<Textarea placeholder="Hello world" id="description" {...register("description")}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button>Submit</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Compose;
|
||||||
|
|
@ -0,0 +1,213 @@
|
||||||
|
|
||||||
|
export const mails = [
|
||||||
|
{
|
||||||
|
id: "fe3aa1aa-c89d-4539-8301-8bf18bd80e87",
|
||||||
|
image: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-4.png",
|
||||||
|
label: "Mahedi Amin",
|
||||||
|
value: "mahedi",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: "laboriosam mollitia et enim quasi adipisci quia provident illum",
|
||||||
|
isDone: false,
|
||||||
|
name: "Ester Howard",
|
||||||
|
isfav: false,
|
||||||
|
time: "12.00 pm",
|
||||||
|
isTrash: false,
|
||||||
|
category: [
|
||||||
|
{
|
||||||
|
value: "team",
|
||||||
|
label: "team",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "9d7135af-2c69-44f6-a1b5-e7c42126e1e9",
|
||||||
|
image: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-2.png",
|
||||||
|
label: "Rakibul Islam",
|
||||||
|
value: "rakibul",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title:
|
||||||
|
"Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint.",
|
||||||
|
isDone: false,
|
||||||
|
name: "Ester Howard",
|
||||||
|
isfav: true,
|
||||||
|
time: "12.00 pm",
|
||||||
|
isTrash: false,
|
||||||
|
category: [
|
||||||
|
{
|
||||||
|
value: "low",
|
||||||
|
label: "low",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "fff45fba-157c-4a9a-a839-480e4c94f927",
|
||||||
|
image: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-1.png",
|
||||||
|
label: "Rakibul Islam",
|
||||||
|
value: "rakibul",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title:
|
||||||
|
"Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint.",
|
||||||
|
isDone: true,
|
||||||
|
isfav: true,
|
||||||
|
time: "12.00 pm",
|
||||||
|
name: "Ester Howard",
|
||||||
|
isTrash: false,
|
||||||
|
category: [
|
||||||
|
{
|
||||||
|
value: "medium",
|
||||||
|
label: "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "low",
|
||||||
|
label: "low",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "4c5bd598-f951-4a4e-981d-b1f0af0cc917",
|
||||||
|
image: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-3.png",
|
||||||
|
label: "Mahedi Amin",
|
||||||
|
value: "mahedi",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: "illo expedita consequatur quia in",
|
||||||
|
isDone: false,
|
||||||
|
name: "Ester Howard",
|
||||||
|
isfav: false,
|
||||||
|
time: "12.00 pm",
|
||||||
|
isTrash: false,
|
||||||
|
category: [
|
||||||
|
{
|
||||||
|
value: "high",
|
||||||
|
label: "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "low",
|
||||||
|
label: "low",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "bc1ad0b1-d6f8-42e1-9689-21c8f2814396",
|
||||||
|
image: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-5.png",
|
||||||
|
label: "Rakibul Islam",
|
||||||
|
value: "rakibul",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: "illo expedita consequatur quia in",
|
||||||
|
isDone: false,
|
||||||
|
name: "Ester Howard",
|
||||||
|
isfav: false,
|
||||||
|
time: "12.00 pm",
|
||||||
|
isTrash: false,
|
||||||
|
category: [
|
||||||
|
{
|
||||||
|
value: "update",
|
||||||
|
label: "update",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "6f4eabf5-e549-4fd1-a24f-87093ebd31be",
|
||||||
|
image: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-5.png",
|
||||||
|
label: "Rakibul Islam",
|
||||||
|
value: "rakibul",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: "illo expedita consequatur quia in",
|
||||||
|
isDone: false,
|
||||||
|
name: "Ester Howard",
|
||||||
|
isfav: false,
|
||||||
|
time: "12.00 pm",
|
||||||
|
isTrash: false,
|
||||||
|
category: [
|
||||||
|
{
|
||||||
|
value: "update",
|
||||||
|
label: "update",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ed6df190-9141-4048-81d0-1ac067f37e46",
|
||||||
|
image: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-5.png",
|
||||||
|
label: "Rakibul Islam",
|
||||||
|
value: "rakibul",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: "illo expedita consequatur quia in",
|
||||||
|
isDone: false,
|
||||||
|
name: "Ester Howard",
|
||||||
|
isfav: false,
|
||||||
|
time: "12.00 pm",
|
||||||
|
isTrash: false,
|
||||||
|
category: [
|
||||||
|
{
|
||||||
|
value: "update",
|
||||||
|
label: "update",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "a296f702-a7ed-4127-a42a-121669b02b1a",
|
||||||
|
image: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-5.png",
|
||||||
|
label: "Rakibul Islam",
|
||||||
|
value: "rakibul",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: "illo expedita consequatur quia in",
|
||||||
|
isDone: false,
|
||||||
|
name: "Ester Howard",
|
||||||
|
isfav: false,
|
||||||
|
time: "12.00 pm",
|
||||||
|
isTrash: false,
|
||||||
|
category: [
|
||||||
|
{
|
||||||
|
value: "update",
|
||||||
|
label: "update",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "6d4be8c8-a382-4d4a-8df5-a2234e69b57f",
|
||||||
|
image: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-5.png",
|
||||||
|
label: "Rakibul Islam",
|
||||||
|
value: "rakibul",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: "illo expedita consequatur quia in",
|
||||||
|
isDone: false,
|
||||||
|
name: "Ester Howard",
|
||||||
|
isfav: false,
|
||||||
|
time: "12.00 pm",
|
||||||
|
isTrash: false,
|
||||||
|
category: [
|
||||||
|
{
|
||||||
|
value: "update",
|
||||||
|
label: "update",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export type Mail = (typeof mails)[number];
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Dashcode Next Js",
|
||||||
|
description: "Dashcode is a popular dashboard template.",
|
||||||
|
};
|
||||||
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import MailWrapper from "./mail-wrapper";
|
||||||
|
import MailSidebarWrapper from "./sidebar-wrapper";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import Compose from "./compose";
|
||||||
|
import Nav from "@/components/nav";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Mail",
|
||||||
|
description: "Mail Application",
|
||||||
|
};
|
||||||
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
|
||||||
|
return <MailWrapper>
|
||||||
|
<div className="flex gap-5 h-full ">
|
||||||
|
<MailSidebarWrapper>
|
||||||
|
<Card className=" h-full ">
|
||||||
|
<CardContent className=" h-full space-y-2 pt-6 px-0">
|
||||||
|
<div className="px-5">
|
||||||
|
<Compose />
|
||||||
|
</div>
|
||||||
|
<ScrollArea className="h-[calc(100%-30px)]">
|
||||||
|
<Nav
|
||||||
|
links={[
|
||||||
|
{
|
||||||
|
title: "Inbox",
|
||||||
|
icon: "uil:image-v",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Starred",
|
||||||
|
icon: "heroicons:star",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Sent",
|
||||||
|
icon: "heroicons:paper-airplane",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Drafts",
|
||||||
|
icon: "heroicons:pencil-square",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Spam",
|
||||||
|
icon: "heroicons:information-circle",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Trash",
|
||||||
|
icon: "heroicons:trash",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<div className="space-y-2 mt-3">
|
||||||
|
<p className="text-sm font-medium text-default-900 px-5">TAGS</p>
|
||||||
|
<Nav
|
||||||
|
dotStyle
|
||||||
|
links={[
|
||||||
|
{
|
||||||
|
title: "personal",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "social",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "promotions",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "business",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</MailSidebarWrapper>
|
||||||
|
<div className="flex-1 w-full h-full">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MailWrapper>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { MoreVertical, Search } from "lucide-react";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import ToggleMailSidebar from "./toggle-mail-sidebar";
|
||||||
|
|
||||||
|
const MailHeader = () => {
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
name: "Reset Sort",
|
||||||
|
icon: "heroicons-outline:sort-ascending",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sort A-Z ",
|
||||||
|
icon: "heroicons-outline:sort-ascending",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sort Z-A ",
|
||||||
|
icon: "heroicons-outline:sort-descending",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex-1 flex items-center gap-1">
|
||||||
|
<ToggleMailSidebar />
|
||||||
|
<Checkbox className="w-4 h-4 text-default-400 dark:bg-default-300" />
|
||||||
|
<Input
|
||||||
|
placeholder="Search Mail"
|
||||||
|
className="max-w-[180px] border-none font-medium dark:bg-transparent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-none">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button size="icon" className="w-8 h-8 rounded-full bg-default-100">
|
||||||
|
<MoreVertical className="w-5 h-5 text-default hover:text-default-foreground" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
align="end"
|
||||||
|
className="p-0 rounded-md overflow-hidden"
|
||||||
|
>
|
||||||
|
{actions.map((action, index) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={`action-${index}`}
|
||||||
|
className="flex items-center gap-1.5 p-2 border-b text-default-600 group focus:bg-default focus:text-primary-foreground rounded-none group"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon={action.icon}
|
||||||
|
className="group-hover:text-primary-foreground w-4 h-4"
|
||||||
|
/>
|
||||||
|
<span className="text-default-700 group-hover:text-primary-foreground">
|
||||||
|
{action.name}
|
||||||
|
</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MailHeader;
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
'use client'
|
||||||
|
import { useMediaQuery } from '@/hooks/use-media-query';
|
||||||
|
import { useMailConfig } from '@/hooks/use-mail'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const MailWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [mailConfig, setMailConfig] = useMailConfig();
|
||||||
|
const { isOpen } = mailConfig
|
||||||
|
const isTablet = useMediaQuery("(min-width: 1024px)");
|
||||||
|
return (
|
||||||
|
<div className=' relative app-height'>
|
||||||
|
{!isTablet && isOpen && (
|
||||||
|
<div
|
||||||
|
onClick={() => setMailConfig({ ...mailConfig, isOpen: false })}
|
||||||
|
className="overlay bg-default-900 dark:bg-default-900 dark:bg-opacity-60 bg-opacity-60 backdrop-filter
|
||||||
|
backdrop-blur-sm absolute w-full flex-1 inset-0 z-20 rounded-md"
|
||||||
|
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MailWrapper
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
"use client";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import { AvatarFallback } from "@radix-ui/react-avatar";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
|
import {ReactNode,useState} from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Trash2 } from "lucide-react";
|
||||||
|
import DeleteConfirmationDialog from "@/components/delete-confirmation-dialog";
|
||||||
|
import { Link } from "@/components/navigation"
|
||||||
|
const MailItem = ({ mail }: { mail: any }) => {
|
||||||
|
const { image, title, isfav, time, name, id } = mail;
|
||||||
|
const [open, setOpen] = useState<boolean>(false)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DeleteConfirmationDialog
|
||||||
|
open={open}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
/>
|
||||||
|
<Link href={`/app/email/${id}`} className=" group block border-b border-default-200 dark:border-default-300 last:border-none" >
|
||||||
|
<div className="flex items-center gap-4 group-hover:bg-default-50 last:border-none px-6 py-4 translate-y-0">
|
||||||
|
<div>
|
||||||
|
<Checkbox className="mt-0.5 dark:bg-default-300" />
|
||||||
|
</div>
|
||||||
|
<div className="ms-1">
|
||||||
|
{isfav ? (
|
||||||
|
<Icon
|
||||||
|
icon="heroicons:star-20-solid"
|
||||||
|
className="text-xl cursor-pointer text-[#FFCE30]"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Icon
|
||||||
|
icon="heroicons:star"
|
||||||
|
className="text-xl cursor-pointer text-default-400"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="-space-x-1.5 rtl:space-x-reverse">
|
||||||
|
{image.map(
|
||||||
|
(
|
||||||
|
item: {
|
||||||
|
label: ReactNode;
|
||||||
|
image: string;
|
||||||
|
},
|
||||||
|
index: any
|
||||||
|
) => (
|
||||||
|
<TooltipProvider key={`avatar-${index}`}>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Avatar className="h-7 w-7 border-none shadow-none bg-transparent">
|
||||||
|
<AvatarImage src={item.image} />
|
||||||
|
<AvatarFallback>SA</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{item.label}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 overflow-hidden text-sm text-default-600 truncate">
|
||||||
|
<span className="me-3">{name}</span>
|
||||||
|
<span className="text-sm text-default-600 truncate max-w-56">{title}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<p className="text-default-900 text-sm">{time}</p>
|
||||||
|
<div className="group-hover:bg-default-100 hidden group-hover:flex absolute group-hover:transition-all duration-300 right-0 top-0 h-full items-center ">
|
||||||
|
<Button
|
||||||
|
className="bg-transparent ring-transparent hover:bg-transparent hover:ring-0 hover:ring-offset-0 hover:ring-transparent w-28 border-transparent"
|
||||||
|
size="icon"
|
||||||
|
onClick={(event) => {
|
||||||
|
setOpen(true);
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 className="text-default-900 w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MailItem;
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
|
import MailItem from "./mail";
|
||||||
|
import MailHeader from "./mail-header";
|
||||||
|
import { getMail } from "./utils";
|
||||||
|
const EmailApp = async () => {
|
||||||
|
const mails = await getMail()
|
||||||
|
return (
|
||||||
|
<Card className="h-full rounded-md">
|
||||||
|
<CardHeader className="border-b border-default-200 sticky top-0 z-10 bg-card py-4">
|
||||||
|
<MailHeader />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-0 h-full">
|
||||||
|
<div className="h-[calc(100%-80px)] overflow-y-auto no-scrollbar">
|
||||||
|
{mails.map((mail, index) => (
|
||||||
|
<MailItem key={`mail-${index}`} mail={mail} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmailApp;
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
'use client'
|
||||||
|
import { useMediaQuery } from '@/hooks/use-media-query'
|
||||||
|
import { useMailConfig } from '@/hooks/use-mail'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import React from 'react'
|
||||||
|
const MailSidebarWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [mailConfig] = useMailConfig()
|
||||||
|
const { isOpen } = mailConfig
|
||||||
|
const isTablet = useMediaQuery("(min-width: 1024px)");
|
||||||
|
if (!isTablet) {
|
||||||
|
return (
|
||||||
|
<div className={cn('absolute h-full start-0 w-[240px] z-50 ', {
|
||||||
|
'-start-full': !isOpen
|
||||||
|
})}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className='flex-none md:w-[270px] w-[200px] '>{children}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MailSidebarWrapper
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
"use client";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||||
|
import { useMailConfig } from "@/hooks/use-mail";
|
||||||
|
import { Menu } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const ToggleMailSidebar = () => {
|
||||||
|
const isTablet = useMediaQuery("(min-width: 1024px)");
|
||||||
|
const [mailConfig, setMailConfig] = useMailConfig();
|
||||||
|
if (isTablet) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
color="secondary"
|
||||||
|
onClick={() =>
|
||||||
|
setMailConfig({ ...mailConfig, isOpen: !mailConfig.isOpen })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Menu className=" h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ToggleMailSidebar;
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { mails, Mail} from "./data"
|
||||||
|
|
||||||
|
// get mail
|
||||||
|
|
||||||
|
export const getMail = async () => {
|
||||||
|
return mails
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// get mail by id
|
||||||
|
|
||||||
|
export const getMailById = async (mailId: string) => {
|
||||||
|
return mails.find(mail => mail.id === mailId)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { Plus } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
const AddBoard = () => {
|
||||||
|
const t = useTranslations("KanbanApp")
|
||||||
|
return (
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button>
|
||||||
|
<Plus className="h-4 w-4 me-1" />
|
||||||
|
{t("addBoard")}
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create a new board</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<form className="space-y-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="board-name">Board Name</Label>
|
||||||
|
<Input id="board-name" placeholder="Board Name" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="board-color">Board Color</Label>
|
||||||
|
<Input id="board-color" type="color" placeholder="Board Name" className="p-0 border-0 shadow-none" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button type="submit">Add</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddBoard;
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
'use client'
|
||||||
|
import { SortableContext, useSortable } from "@dnd-kit/sortable";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card";
|
||||||
|
import { CSS } from "@dnd-kit/utilities";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip"
|
||||||
|
|
||||||
|
import { Column, Task } from "./data";
|
||||||
|
import TaskCard from "./task";
|
||||||
|
import { Plus, Trash2 } from "lucide-react";
|
||||||
|
import EmptyTask from "./empty";
|
||||||
|
|
||||||
|
import DeleteConfirmationDialog from "@/components/delete-confirmation-dialog";
|
||||||
|
|
||||||
|
function ColumnContainer({ column, tasks, handleOpenTask }: { column: Column; tasks: Task[], handleOpenTask: () => void }) {
|
||||||
|
const [editMode, setEditMode] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [deleteColumn, setDeleteColumn] = useState<boolean>(false);
|
||||||
|
const tasksIds = useMemo(() => {
|
||||||
|
return tasks.map((task) => task.id);
|
||||||
|
}, [tasks]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
setNodeRef,
|
||||||
|
attributes,
|
||||||
|
listeners,
|
||||||
|
transform,
|
||||||
|
transition,
|
||||||
|
isDragging,
|
||||||
|
} = useSortable({
|
||||||
|
id: column.id,
|
||||||
|
data: {
|
||||||
|
type: "Column",
|
||||||
|
column,
|
||||||
|
},
|
||||||
|
disabled: editMode,
|
||||||
|
});
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
transition,
|
||||||
|
transform: CSS.Transform.toString(transform),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DeleteConfirmationDialog
|
||||||
|
open={deleteColumn}
|
||||||
|
onClose={() => setDeleteColumn(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
ref={setNodeRef}
|
||||||
|
style={style}
|
||||||
|
className={cn("flex-1 w-[280px] bg-default-200 shadow-none app-height flex flex-col relative", {
|
||||||
|
"opacity-20": isDragging,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<CardHeader className="flex-none bg-card relative rounded-t-md py-4" {...attributes} {...listeners}>
|
||||||
|
<div className={cn("absolute -start-[1px] top-1/2 -translate-y-1/2 h-[60%] w-0.5 bg-primary",
|
||||||
|
{
|
||||||
|
"bg-warning": (column.title).toLocaleLowerCase() === "work in progress",
|
||||||
|
"bg-success": (column.title).toLocaleLowerCase() === "done",
|
||||||
|
}
|
||||||
|
)}></div>
|
||||||
|
<div className="flex items-center gap-2" >
|
||||||
|
<div className="flex-1 text-lg capitalize text-default-900 font-medium">{column.title}</div>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="w-6 h-6 bg-transparent hover:bg-transparent border border-default-200 text-default-600 hover:ring-0 hover:ring-transparent"
|
||||||
|
onClick={() => setDeleteColumn(true)}
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent className="bg-destructive">
|
||||||
|
<p>Delete</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="w-6 h-6 bg-transparent ring-offset-transparent hover:bg-transparent border border-default-200 text-default-600 hover:ring-0 hover:ring-transparent"
|
||||||
|
onClick={handleOpenTask}
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Add Project</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex-1 pt-6 px-3.5 h-full overflow-y-auto no-scrollbar">
|
||||||
|
{/* Column task container */}
|
||||||
|
<div className=" space-y-6">
|
||||||
|
{tasks?.length === 0 && <EmptyTask />}
|
||||||
|
<SortableContext items={tasksIds}>
|
||||||
|
{tasks.map((task) => (
|
||||||
|
<TaskCard task={task} key={task.id} />
|
||||||
|
))}
|
||||||
|
</SortableContext>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ColumnContainer;
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
"use client";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { CalendarIcon } from "lucide-react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm, SubmitHandler, Controller } from "react-hook-form"
|
||||||
|
import Select, { MultiValue } from 'react-select'
|
||||||
|
interface CreateTaskProps {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
interface Option {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
image?: string;
|
||||||
|
}
|
||||||
|
type Inputs = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tags: MultiValue<Option>;
|
||||||
|
assignee: MultiValue<Option>;
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date;
|
||||||
|
}
|
||||||
|
const options: Option[] = [
|
||||||
|
{
|
||||||
|
value: "team",
|
||||||
|
label: "team",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "low",
|
||||||
|
label: "low",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "medium",
|
||||||
|
label: "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "high",
|
||||||
|
label: "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "update",
|
||||||
|
label: "update",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const assigneeOptions: Option[] = [
|
||||||
|
{ value: "mahedi", label: "Mahedi Amin", image: "/images/avatar/av-1.svg" },
|
||||||
|
{ value: "sovo", label: "Sovo Haldar", image: "/images/avatar/av-2.svg" },
|
||||||
|
{ value: "rakibul", label: "Rakibul Islam", image: "/images/avatar/av-3.svg" },
|
||||||
|
{ value: "pritom", label: "Pritom Miha", image: "/images/avatar/av-4.svg" },
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const CreateTask = ({ open, setOpen }: CreateTaskProps) => {
|
||||||
|
const [startDate, setStartDate] = useState<Date>(new Date());
|
||||||
|
const [endDate, setEndtDate] = useState<Date>(new Date());
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
control,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<Inputs>()
|
||||||
|
const onSubmit: SubmitHandler<Inputs> = (data) => {
|
||||||
|
console.log(data)
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create Task</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="projectName">Project Name</Label>
|
||||||
|
<Input
|
||||||
|
id="projectName"
|
||||||
|
placeholder="Project Name"
|
||||||
|
{...register("title", { required: "Title is required." })}
|
||||||
|
color={errors.title ? "destructive" : "default"}
|
||||||
|
/>
|
||||||
|
{errors.title && <p className="text-destructive text-sm font-medium">{errors.title.message}</p>}
|
||||||
|
</div>
|
||||||
|
<div className="grid lg:grid-cols-2 grid-cols-1 gap-5">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="startDate">Start Date</Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full justify-start text-start font-normal border border-default-200 text-default-700 md:px-2.5 hover:bg-transparent hover:text-default-700"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<CalendarIcon className="me-2 h-3.5 w-3.5 text-default-500" />
|
||||||
|
{format(startDate, "PPP")}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<Controller
|
||||||
|
name="startDate"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={startDate}
|
||||||
|
onSelect={(date) => {
|
||||||
|
field.onChange(date);
|
||||||
|
setStartDate(date as Date);
|
||||||
|
}}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="endDate">End Date</Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full justify-start text-start font-normal border border-default-200 text-default-700 md:px-2.5 hover:bg-transparent hover:text-default-700"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<CalendarIcon className="me-2 h-3.5 w-3.5 text-default-500" />
|
||||||
|
{format(endDate, "PPP")}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<Controller
|
||||||
|
name="startDate"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={endDate}
|
||||||
|
onSelect={(date) => {
|
||||||
|
field.onChange(date);
|
||||||
|
setEndtDate(date as Date);
|
||||||
|
}}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="assignee">Assignee</Label>
|
||||||
|
<Controller
|
||||||
|
name="assignee"
|
||||||
|
control={control}
|
||||||
|
defaultValue={[]}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
className="react-select"
|
||||||
|
classNamePrefix="select"
|
||||||
|
{...field}
|
||||||
|
options={assigneeOptions}
|
||||||
|
isMulti
|
||||||
|
onChange={(selectedOption) => field.onChange(selectedOption)}
|
||||||
|
getOptionLabel={(option) => (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Image width={40} height={40} src={option.image as string} alt={option.label} className="w-8 h-8 rounded-full me-2" />
|
||||||
|
<span className="text-sm text-default-600 font-medium">{option.label}</span>
|
||||||
|
</div>
|
||||||
|
) as unknown as string}
|
||||||
|
placeholder="Select assignee"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="tag">Tags</Label>
|
||||||
|
<Controller
|
||||||
|
name="tags"
|
||||||
|
control={control}
|
||||||
|
defaultValue={[]}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
className="react-select"
|
||||||
|
classNamePrefix="select"
|
||||||
|
{...field}
|
||||||
|
options={options}
|
||||||
|
isMulti
|
||||||
|
onChange={(selectedOption) => field.onChange(selectedOption)}
|
||||||
|
placeholder="Select tags"
|
||||||
|
getOptionLabel={(option) => <span className="text-sm text-default-600 font-medium capitalize">{option.label}</span> as unknown as string}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="description">Description</Label>
|
||||||
|
<Textarea
|
||||||
|
id="description"
|
||||||
|
placeholder="Project Description"
|
||||||
|
{...register("description")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button type="submit">Add</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateTask;
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
|
||||||
|
export const defaultCols = [
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Todo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Work in progress",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Done",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const defaultTasks = [
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
columnId: defaultCols[0].id,
|
||||||
|
title: "CRM Dashboard ",
|
||||||
|
projectLogo: "/images/project/p-2.png",
|
||||||
|
desc: "Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint.",
|
||||||
|
startDate: "2022-10-03",
|
||||||
|
endDate: "2022-10-06",
|
||||||
|
progress: 90,
|
||||||
|
assignee: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-1.svg",
|
||||||
|
name: "Mahedi Amin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-2.svg",
|
||||||
|
name: "Sovo Haldar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-3.svg",
|
||||||
|
name: "Rakibul Islam",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
remainingDays: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
columnId: defaultCols[0].id,
|
||||||
|
title: "Business Dashboard ",
|
||||||
|
projectLogo: "/images/project/p-2.png",
|
||||||
|
desc: "Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint.",
|
||||||
|
startDate: "2022-10-03",
|
||||||
|
endDate: "2022-10-06",
|
||||||
|
progress: 90,
|
||||||
|
assignee: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-1.svg",
|
||||||
|
name: "Mahedi Amin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-2.svg",
|
||||||
|
name: "Sovo Haldar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-3.svg",
|
||||||
|
name: "Rakibul Islam",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
remainingDays: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
columnId: defaultCols[1].id,
|
||||||
|
title: "Management Dashboard ",
|
||||||
|
projectLogo: "/images/project/p-2.png",
|
||||||
|
desc: "Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint.",
|
||||||
|
startDate: "2022-10-03",
|
||||||
|
endDate: "2022-10-06",
|
||||||
|
progress: 90,
|
||||||
|
assignee: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-1.svg",
|
||||||
|
name: "Mahedi Amin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-2.svg",
|
||||||
|
name: "Sovo Haldar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-3.svg",
|
||||||
|
name: "Rakibul Islam",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
remainingDays: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
columnId: defaultCols[1].id,
|
||||||
|
title: "Analytics Dashboard ",
|
||||||
|
projectLogo: "/images/project/p-2.png",
|
||||||
|
desc: "Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint.",
|
||||||
|
startDate: "2022-10-03",
|
||||||
|
endDate: "2022-10-06",
|
||||||
|
progress: 90,
|
||||||
|
assignee: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-1.svg",
|
||||||
|
name: "Mahedi Amin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-2.svg",
|
||||||
|
name: "Sovo Haldar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-3.svg",
|
||||||
|
name: "Rakibul Islam",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
remainingDays: 3
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
columnId: defaultCols[1].id,
|
||||||
|
title: "Marketing Dashboard ",
|
||||||
|
projectLogo: "/images/project/p-2.png",
|
||||||
|
desc: "Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint.",
|
||||||
|
startDate: "2022-10-03",
|
||||||
|
endDate: "2022-10-06",
|
||||||
|
progress: 90,
|
||||||
|
assignee: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-1.svg",
|
||||||
|
name: "Mahedi Amin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-2.svg",
|
||||||
|
name: "Sovo Haldar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-3.svg",
|
||||||
|
name: "Rakibul Islam",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
remainingDays: 3
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
export type Column = (typeof defaultCols)[number];
|
||||||
|
|
||||||
|
export type Task = (typeof defaultTasks)[number]
|
||||||
|
|
@ -0,0 +1,226 @@
|
||||||
|
"use client";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { CalendarIcon } from "lucide-react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm, SubmitHandler, Controller } from "react-hook-form"
|
||||||
|
import Select, { MultiValue } from 'react-select'
|
||||||
|
interface EditTaskProps {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
interface Option {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
image?: string;
|
||||||
|
}
|
||||||
|
type Inputs = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tags: MultiValue<Option>;
|
||||||
|
assignee: MultiValue<Option>;
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date;
|
||||||
|
}
|
||||||
|
const options: Option[] = [
|
||||||
|
{
|
||||||
|
value: "team",
|
||||||
|
label: "team",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "low",
|
||||||
|
label: "low",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "medium",
|
||||||
|
label: "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "high",
|
||||||
|
label: "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "update",
|
||||||
|
label: "update",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const assigneeOptions: Option[] = [
|
||||||
|
{ value: "mahedi", label: "Mahedi Amin", image: "/images/avatar/av-1.svg" },
|
||||||
|
{ value: "sovo", label: "Sovo Haldar", image: "/images/avatar/av-2.svg" },
|
||||||
|
{ value: "rakibul", label: "Rakibul Islam", image: "/images/avatar/av-3.svg" },
|
||||||
|
{ value: "pritom", label: "Pritom Miha", image: "/images/avatar/av-4.svg" },
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const EditTask = ({ open, setOpen }: EditTaskProps) => {
|
||||||
|
const [startDate, setStartDate] = useState<Date>(new Date());
|
||||||
|
const [endDate, setEndtDate] = useState<Date>(new Date());
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
control,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<Inputs>()
|
||||||
|
const onSubmit: SubmitHandler<Inputs> = (data) => {
|
||||||
|
console.log(data)
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create Task</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="projectName">Project Name</Label>
|
||||||
|
<Input
|
||||||
|
id="projectName"
|
||||||
|
placeholder="Project Name"
|
||||||
|
defaultValue="Project Title"
|
||||||
|
{...register("title", { required: "Title is required." })}
|
||||||
|
color={errors.title ? "destructive" : "default"}
|
||||||
|
/>
|
||||||
|
{errors.title && <p className="text-destructive text-sm font-medium">{errors.title.message}</p>}
|
||||||
|
</div>
|
||||||
|
<div className="grid lg:grid-cols-2 grid-cols-1 gap-5">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="startDate">Start Date</Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full justify-start text-start font-normal border border-default-200 text-default-700 md:px-2.5 hover:bg-transparent hover:text-default-700"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<CalendarIcon className="me-2 h-3.5 w-3.5 text-default-500" />
|
||||||
|
{format(startDate, "PPP")}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<Controller
|
||||||
|
name="startDate"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={startDate}
|
||||||
|
onSelect={(date) => {
|
||||||
|
field.onChange(date);
|
||||||
|
setStartDate(date as Date);
|
||||||
|
}}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="endDate">End Date</Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full justify-start text-start font-normal border border-default-200 text-default-700 md:px-2.5 hover:bg-transparent hover:text-default-700"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<CalendarIcon className="me-2 h-3.5 w-3.5 text-default-500" />
|
||||||
|
{format(endDate, "PPP")}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<Controller
|
||||||
|
name="startDate"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={endDate}
|
||||||
|
onSelect={(date) => {
|
||||||
|
field.onChange(date);
|
||||||
|
setEndtDate(date as Date);
|
||||||
|
}}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="assignee">Assignee</Label>
|
||||||
|
<Controller
|
||||||
|
name="assignee"
|
||||||
|
control={control}
|
||||||
|
defaultValue={[{ value: "mahedi", label: "Mahedi Amin", image: "/images/avatar/av-1.svg" }]}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
{...field}
|
||||||
|
options={assigneeOptions}
|
||||||
|
isMulti
|
||||||
|
onChange={(selectedOption) => field.onChange(selectedOption)}
|
||||||
|
getOptionLabel={(option) => (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Image width={40} height={40} src={option.image as string} alt={option.label} className="w-8 h-8 rounded-full me-2" />
|
||||||
|
<span className="text-sm text-default-600 font-medium">{option.label}</span>
|
||||||
|
</div>
|
||||||
|
) as unknown as string}
|
||||||
|
placeholder="Select assignee"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="tag">Tags</Label>
|
||||||
|
<Controller
|
||||||
|
name="tags"
|
||||||
|
control={control}
|
||||||
|
defaultValue={[{ value: "team", label: "Team" }]}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
{...field}
|
||||||
|
options={options}
|
||||||
|
isMulti
|
||||||
|
onChange={(selectedOption) => field.onChange(selectedOption)}
|
||||||
|
placeholder="Select tags"
|
||||||
|
getOptionLabel={(option) => <span className="text-sm text-default-600 font-medium capitalize">{option.label}</span> as unknown as string}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="description">Description</Label>
|
||||||
|
<Textarea
|
||||||
|
id="description"
|
||||||
|
placeholder="Project Description"
|
||||||
|
{...register("description")}
|
||||||
|
defaultValue="Project Description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button type="submit">Add</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditTask;
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
import { Rabbit } from 'lucide-react';
|
||||||
|
|
||||||
|
|
||||||
|
const EmptyTask = () => {
|
||||||
|
return (
|
||||||
|
<div className=" absolute start-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||||
|
<Rabbit className=" h-[200px] w-[200px] text-default-400 " strokeWidth={1} />
|
||||||
|
<div className=' text-sm text-default-500 text-center'>No tasks</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmptyTask;
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
'use client'
|
||||||
|
import React, { useState, useMemo } from 'react'
|
||||||
|
import { type Column, type Task } from './data';
|
||||||
|
import {
|
||||||
|
DndContext,
|
||||||
|
DragEndEvent,
|
||||||
|
DragOverEvent,
|
||||||
|
DragOverlay,
|
||||||
|
DragStartEvent,
|
||||||
|
PointerSensor,
|
||||||
|
useSensor,
|
||||||
|
useSensors,
|
||||||
|
} from "@dnd-kit/core";
|
||||||
|
import { SortableContext, arrayMove } from "@dnd-kit/sortable";
|
||||||
|
import ColumnContainer from "./column"
|
||||||
|
import TaskCard from './task';
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
import AddBoard from './add-board';
|
||||||
|
import CreateTask from "./create-task";
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
const KanBanApp = ({ defaultCols, defaultTasks }: { defaultCols: Column[], defaultTasks: Task[] }) => {
|
||||||
|
const t = useTranslations("KanbanApp");
|
||||||
|
const [columns, setColumns] = useState<Column[]>(defaultCols);
|
||||||
|
const columnsId = useMemo(() => columns.map((col) => col.id), [columns]);
|
||||||
|
|
||||||
|
const [tasks, setTasks] = useState<Task[]>(defaultTasks);
|
||||||
|
|
||||||
|
const [activeColumn, setActiveColumn] = useState<Column | null>(null);
|
||||||
|
|
||||||
|
const [activeTask, setActiveTask] = useState<Task | null>(null);
|
||||||
|
|
||||||
|
// create task state
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
const sensors = useSensors(
|
||||||
|
useSensor(PointerSensor, {
|
||||||
|
activationConstraint: {
|
||||||
|
distance: 10,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
function onDragStart(event: DragStartEvent) {
|
||||||
|
if (event.active.data.current?.type === "Column") {
|
||||||
|
setActiveColumn(event.active.data.current.column);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.active.data.current?.type === "Task") {
|
||||||
|
setActiveTask(event.active.data.current.task);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onDragEnd(event: DragEndEvent) {
|
||||||
|
setActiveColumn(null);
|
||||||
|
setActiveTask(null);
|
||||||
|
|
||||||
|
const { active, over } = event;
|
||||||
|
if (!over) return;
|
||||||
|
|
||||||
|
const activeId = active.id;
|
||||||
|
const overId = over.id;
|
||||||
|
|
||||||
|
if (activeId === overId) return;
|
||||||
|
|
||||||
|
const isActiveAColumn = active.data.current?.type === "Column";
|
||||||
|
if (!isActiveAColumn) return;
|
||||||
|
|
||||||
|
console.log("DRAG END");
|
||||||
|
|
||||||
|
setColumns((columns) => {
|
||||||
|
const activeColumnIndex = columns.findIndex((col) => col.id === activeId);
|
||||||
|
|
||||||
|
const overColumnIndex = columns.findIndex((col) => col.id === overId);
|
||||||
|
|
||||||
|
return arrayMove(columns, activeColumnIndex, overColumnIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function onDragOver(event: DragOverEvent) {
|
||||||
|
const { active, over } = event;
|
||||||
|
if (!over) return;
|
||||||
|
|
||||||
|
const activeId = active.id;
|
||||||
|
const overId = over.id;
|
||||||
|
|
||||||
|
if (activeId === overId) return;
|
||||||
|
|
||||||
|
const isActiveATask = active.data.current?.type === "Task";
|
||||||
|
const isOverATask = over.data.current?.type === "Task";
|
||||||
|
|
||||||
|
if (!isActiveATask) return;
|
||||||
|
|
||||||
|
|
||||||
|
if (isActiveATask && isOverATask) {
|
||||||
|
setTasks((tasks) => {
|
||||||
|
const activeIndex = tasks.findIndex((t) => t.id === activeId);
|
||||||
|
const overIndex = tasks.findIndex((t) => t.id === overId);
|
||||||
|
|
||||||
|
if (tasks[activeIndex].columnId != tasks[overIndex].columnId) {
|
||||||
|
|
||||||
|
tasks[activeIndex].columnId = tasks[overIndex].columnId;
|
||||||
|
return arrayMove(tasks, activeIndex, overIndex - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arrayMove(tasks, activeIndex, overIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOverAColumn = over.data.current?.type === "Column";
|
||||||
|
|
||||||
|
|
||||||
|
if (isActiveATask && isOverAColumn) {
|
||||||
|
setTasks((tasks) => {
|
||||||
|
const activeIndex = tasks.findIndex((t) => t.id === activeId);
|
||||||
|
tasks[activeIndex].columnId = overId.toString();
|
||||||
|
return arrayMove(tasks, activeIndex, activeIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="">
|
||||||
|
<div className="flex gap-2 mb-5">
|
||||||
|
<div className="flex-1 font-medium lg:text-2xl text-xl capitalize text-default-900">
|
||||||
|
{t("title")}
|
||||||
|
</div>
|
||||||
|
<div className="flex-none">
|
||||||
|
<AddBoard />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DndContext
|
||||||
|
sensors={sensors}
|
||||||
|
onDragStart={onDragStart}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
onDragOver={onDragOver}
|
||||||
|
>
|
||||||
|
<div className="flex gap-4 overflow-x-auto no-scrollbar">
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<SortableContext items={columnsId}>
|
||||||
|
{columns.map((col) => (
|
||||||
|
<ColumnContainer
|
||||||
|
key={col.id}
|
||||||
|
column={col}
|
||||||
|
tasks={tasks.filter((task) => task.columnId === col.id)}
|
||||||
|
handleOpenTask={() => setOpen(true)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</SortableContext>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{createPortal(
|
||||||
|
<DragOverlay>
|
||||||
|
{activeColumn && (
|
||||||
|
<ColumnContainer
|
||||||
|
column={activeColumn}
|
||||||
|
handleOpenTask={() => setOpen(true)}
|
||||||
|
tasks={tasks.filter((task) => task.columnId === activeColumn.id)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{activeTask && <TaskCard task={activeTask} />}
|
||||||
|
</DragOverlay>,
|
||||||
|
document.body
|
||||||
|
)}
|
||||||
|
</DndContext>
|
||||||
|
</div>
|
||||||
|
<CreateTask
|
||||||
|
open={open}
|
||||||
|
setOpen={setOpen}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KanBanApp
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Dashcode Next Js",
|
||||||
|
description: "Dashcode is a popular dashboard template.",
|
||||||
|
};
|
||||||
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react'
|
||||||
|
import KanBanApp from './kanban-app'
|
||||||
|
import { defaultCols, defaultTasks } from './data'
|
||||||
|
|
||||||
|
const KanBanPage = () => {
|
||||||
|
return (
|
||||||
|
<KanBanApp
|
||||||
|
defaultCols={defaultCols}
|
||||||
|
defaultTasks={defaultTasks}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KanBanPage
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
"use client"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { useSortable } from "@dnd-kit/sortable";
|
||||||
|
import { CSS } from "@dnd-kit/utilities";
|
||||||
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
|
import { Task } from "./data";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu"
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
||||||
|
import { Progress } from "@/components/ui/progress";
|
||||||
|
import DeleteConfirmationDialog from "@/components/delete-confirmation-dialog";
|
||||||
|
import { useState } from "react";
|
||||||
|
import EditTask from "./edit-task";
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
|
||||||
|
function TaskCard({ task }: { task: Task }) {
|
||||||
|
const { projectLogo, title, desc, startDate, endDate, progress, assignee, remainingDays } = task;
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
const [editTaskOpen, setEditTaskOpen] = useState<boolean>(false);
|
||||||
|
const {
|
||||||
|
setNodeRef,
|
||||||
|
attributes,
|
||||||
|
listeners,
|
||||||
|
transform,
|
||||||
|
transition,
|
||||||
|
isDragging,
|
||||||
|
} = useSortable({
|
||||||
|
id: task.id,
|
||||||
|
data: {
|
||||||
|
type: "Task",
|
||||||
|
task,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
transition,
|
||||||
|
transform: CSS.Transform.toString(transform),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DeleteConfirmationDialog
|
||||||
|
open={open}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
/>
|
||||||
|
<EditTask
|
||||||
|
open={editTaskOpen}
|
||||||
|
setOpen={setEditTaskOpen}
|
||||||
|
/>
|
||||||
|
<Card
|
||||||
|
className={cn("", {
|
||||||
|
"opacity-10 bg-primary/50 ": isDragging,
|
||||||
|
})}
|
||||||
|
ref={setNodeRef}
|
||||||
|
style={style}
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
>
|
||||||
|
<CardHeader className="flex-row gap-1 p-2.5 items-center space-y-0">
|
||||||
|
<Avatar className="flex-none h-8 w-8 rounded bg-default-200 text-default hover:bg-default-200">
|
||||||
|
<AvatarImage src={projectLogo} />
|
||||||
|
<AvatarFallback className="uppercase"> {title.charAt(0) + title.charAt(1)}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<h3 className="flex-1 text-default-800 text-lg font-medium max-w-[160px] truncate text-center capitalize ">{title}</h3>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="flex-none ring-offset-transparent bg-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent w-6"
|
||||||
|
>
|
||||||
|
<MoreVertical className="h-4 w-4 text-default-900" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="p-0 overflow-hidden" align="end" >
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="py-2 border-b border-default-200 text-default-600 focus:bg-default focus:text-default-foreground rounded-none cursor-pointer"
|
||||||
|
onClick={() => setEditTaskOpen(true)}
|
||||||
|
>
|
||||||
|
<SquarePen className="w-3.5 h-3.5 me-1" />
|
||||||
|
Edit
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="py-2 bg-destructive/30 text-destructive focus:bg-destructive focus:text-destructive-foreground rounded-none cursor-pointer"
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
>
|
||||||
|
<Trash2 className="w-3.5 h-3.5 me-1" />
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-2.5 pt-1">
|
||||||
|
<div className="text-default-600 text-sm">{desc}</div>
|
||||||
|
<div className="flex gap-4 mt-6">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-default-400 mb-1">Start Date</div>
|
||||||
|
<div className="text-xs text-default-600 font-medium">{startDate}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-default-400 mb-1">End Date</div>
|
||||||
|
<div className="text-xs text-default-600 font-medium">{endDate}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-1">
|
||||||
|
<div className="text-end text-xs text-default-600 mb-1.5 font-medium">{progress}%</div>
|
||||||
|
<Progress value={progress} color="primary" size="sm" />
|
||||||
|
</div>
|
||||||
|
<div className="flex mt-5">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="text-default-400 text-sm font-normal mb-3">Assigned to</div>
|
||||||
|
<div className="flex items-center -space-x-1">
|
||||||
|
{
|
||||||
|
assignee?.map((user, index) => (
|
||||||
|
<Avatar
|
||||||
|
key={`user-${index}`}
|
||||||
|
className="h-6 w-6"
|
||||||
|
>
|
||||||
|
<AvatarImage src={user.image} />
|
||||||
|
<AvatarFallback> {user.name.charAt(0) + user.name.charAt(1)}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
<div className="bg-card text-default-900 text-xs ring-2 ring-default-100 rounded-full h-6 w-6 flex flex-col justify-center items-center">
|
||||||
|
+2
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-none">
|
||||||
|
<div className="flex items-center gap-1 bg-destructive/10 text-destructive rounded-full px-2 py-0.5 text-sm mt-1">
|
||||||
|
<Icon icon="heroicons-outline:clock" />
|
||||||
|
{remainingDays} days left
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TaskCard;
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Dashcode Next Js",
|
||||||
|
description: "Dashcode is a popular dashboard template.",
|
||||||
|
};
|
||||||
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
|
|
@ -0,0 +1,220 @@
|
||||||
|
|
||||||
|
export const meets = [
|
||||||
|
{
|
||||||
|
img: "/images/svg/sk.svg",
|
||||||
|
title: "Meeting with client",
|
||||||
|
date: "01 Nov 2021",
|
||||||
|
meet: "Zoom meeting",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: "/images/svg/path.svg",
|
||||||
|
title: "Design meeting (team)",
|
||||||
|
date: "01 Nov 2021",
|
||||||
|
meet: "Skyp meeting",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: "/images/svg/dc.svg",
|
||||||
|
title: "Background research",
|
||||||
|
date: "01 Nov 2021",
|
||||||
|
meet: "Google meeting",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: "/images/svg/sk.svg",
|
||||||
|
title: "Meeting with client",
|
||||||
|
date: "01 Nov 2021",
|
||||||
|
meet: "Zoom meeting",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const tasks = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
image: "/images/users/user-1.jpg",
|
||||||
|
title: "Amet minim mollit non deserunt ullam.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
image: "/images/users/user-2.jpg",
|
||||||
|
title: "Amet minim mollit non deserunt ullam.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
image: "/images/users/user-3.jpg",
|
||||||
|
title: "Amet minim mollit non deserunt ullam.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
image: "/images/users/user-4.jpg",
|
||||||
|
title: "Amet minim mollit non deserunt ullam.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
image: "/images/users/user-5.jpg",
|
||||||
|
title: "Amet minim mollit non deserunt ullam.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
image: "/images/users/user-6.jpg",
|
||||||
|
title: "Amet minim mollit non deserunt ullam.",
|
||||||
|
}]
|
||||||
|
|
||||||
|
export const messagesData = [
|
||||||
|
{
|
||||||
|
title: "Wade Warren",
|
||||||
|
desc: "Hi! How are you doing?.....",
|
||||||
|
active: true,
|
||||||
|
hasnotifaction: true,
|
||||||
|
notification_count: 1,
|
||||||
|
image: "/images/users/user-1.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Savannah Nguyen",
|
||||||
|
desc: "Hi! How are you doing?.....",
|
||||||
|
active: false,
|
||||||
|
hasnotifaction: false,
|
||||||
|
image: "/images/users/user-2.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Ralph Edwards",
|
||||||
|
desc: "Hi! How are you doing?.....",
|
||||||
|
active: false,
|
||||||
|
hasnotifaction: true,
|
||||||
|
notification_count: 8,
|
||||||
|
image: "/images/users/user-3.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Cody Fisher",
|
||||||
|
desc: "Hi! How are you doing?.....",
|
||||||
|
active: true,
|
||||||
|
hasnotifaction: false,
|
||||||
|
image: "/images/users/user-4.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Savannah Nguyen",
|
||||||
|
desc: "Hi! How are you doing?.....",
|
||||||
|
active: false,
|
||||||
|
hasnotifaction: false,
|
||||||
|
image: "/images/users/user-4.jpg"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const activityList = [
|
||||||
|
{
|
||||||
|
title: "Project start date",
|
||||||
|
desc: "This parcel is paid for by the customer. Please contact the customer for any further information.",
|
||||||
|
date: "Sep 20, 2021 ",
|
||||||
|
time: "12:32 AM",
|
||||||
|
status: "ok",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Project start date",
|
||||||
|
date: "Sep 20, 2021 ",
|
||||||
|
desc: "This parcel is paid for by the customer. Please contact the customer for any further information.",
|
||||||
|
time: "12:32 AM",
|
||||||
|
status: "ok",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Project start date",
|
||||||
|
date: "Sep 20, 2021 ",
|
||||||
|
desc: "This parcel is paid for by the customer. Please contact the customer for any further information.",
|
||||||
|
time: "12:32 AM",
|
||||||
|
status: "ok",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Project start date",
|
||||||
|
date: "Sep 20, 2021 ",
|
||||||
|
desc: "This parcel is paid for by the customer. Please contact the customer for any further information.",
|
||||||
|
time: "12:32 AM",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Project start date",
|
||||||
|
date: "Sep 20, 2021 ",
|
||||||
|
desc: "This parcel is paid for by the customer. Please contact the customer for any further information.",
|
||||||
|
time: "12:32 AM",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const files = [
|
||||||
|
{
|
||||||
|
img: "/images/icon/file-1.svg",
|
||||||
|
title: "Dashboard.fig",
|
||||||
|
date: "06 June 2021 / 155MB",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: "/images/icon/pdf-1.svg",
|
||||||
|
title: "Ecommerce.pdf",
|
||||||
|
date: "06 June 2021 / 155MB",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: "/images/icon/zip-1.svg",
|
||||||
|
title: "Job portal_app.zip",
|
||||||
|
date: "06 June 2021 / 155MB",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: "/images/icon/pdf-2.svg",
|
||||||
|
title: "Ecommerce.pdf",
|
||||||
|
date: "06 June 2021 / 155MB",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: "/images/icon/scr-1.svg",
|
||||||
|
title: "Screenshot.jpg",
|
||||||
|
date: "06 June 2021 / 155MB",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
export const teamData = [
|
||||||
|
{
|
||||||
|
customer: {
|
||||||
|
name: "Arlene McCoy",
|
||||||
|
image: "/images/users/user-1.jpg",
|
||||||
|
deg: "Ux designer",
|
||||||
|
},
|
||||||
|
status: "progress",
|
||||||
|
time: "42.5 hours",
|
||||||
|
chart: null,
|
||||||
|
action: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
customer: {
|
||||||
|
name: "Arlene McCoy",
|
||||||
|
image: "/images/users/user-2.jpg",
|
||||||
|
deg: "Ux designer",
|
||||||
|
},
|
||||||
|
status: "complete",
|
||||||
|
time: "42.5 hours",
|
||||||
|
chart: null,
|
||||||
|
action: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
customer: {
|
||||||
|
name: "Arlene McCoy",
|
||||||
|
image: "/images/users/user-3.jpg",
|
||||||
|
deg: "Ux designer",
|
||||||
|
},
|
||||||
|
status: "progress",
|
||||||
|
time: "42.5 hours",
|
||||||
|
chart: null,
|
||||||
|
action: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
customer: {
|
||||||
|
name: "Arlene McCoy",
|
||||||
|
image: "/images/users/user-4.jpg",
|
||||||
|
deg: "Ux designer",
|
||||||
|
},
|
||||||
|
status: "complete",
|
||||||
|
time: "42.5 hours",
|
||||||
|
chart: null,
|
||||||
|
action: null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export type TeamDataProps = (typeof teamData)[]
|
||||||
|
|
@ -0,0 +1,335 @@
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { BarChart } from "lucide-react";
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
import ProgressBlock from "@/components/blocks/progress-block";
|
||||||
|
import { Link } from '@/i18n/routing';
|
||||||
|
|
||||||
|
import { meets, tasks, messagesData, activityList, teamData, files } from "./data"
|
||||||
|
import Image from "next/image";
|
||||||
|
import DashboardDropdown from "@/components/dashboard-dropdown";
|
||||||
|
|
||||||
|
import TaskItem from "@/components/project/task-item";
|
||||||
|
import MessageListItem from "@/components/project/message-list-item";
|
||||||
|
import ActivityItem from "@/components/project/activity";
|
||||||
|
import TeamTable from "@/components/project/team-table";
|
||||||
|
import NotesCalendar from "@/components/project/notes-calendar";
|
||||||
|
import { getProjectById } from "../data";
|
||||||
|
import { Alert } from "@/components/ui/alert";
|
||||||
|
|
||||||
|
const SinglePage = async ({ params: { id } }: { params: { id: string }; }) => {
|
||||||
|
const project = await getProjectById(id)
|
||||||
|
if (!project) return <Alert color="destructive"> project id is not valid</Alert>
|
||||||
|
return (
|
||||||
|
<div className="space-y-5">
|
||||||
|
<div className="grid grid-cols-12 gap-5">
|
||||||
|
|
||||||
|
<Card className="col-span-12 xl:col-span-3">
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="lg:col-span-1 col-span-2">
|
||||||
|
<Card className="bg-info/20 shadow-none border-none">
|
||||||
|
<CardContent className=" p-4 text-center">
|
||||||
|
<div className="mx-auto h-10 w-10 rounded-full flex items-center justify-center bg-white mb-4">
|
||||||
|
<BarChart className=" h-6 w-6 text-info" />
|
||||||
|
</div>
|
||||||
|
<div className="block text-sm text-default-600 font-medium mb-1.5"> Total Task</div>
|
||||||
|
<div className="text-2xl text-default-900 font-medium"> 64</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<div className="lg:col-span-1 col-span-2">
|
||||||
|
<Card className="bg-warning/20 shadow-none border-none">
|
||||||
|
<CardContent className=" p-4 text-center">
|
||||||
|
<div
|
||||||
|
className="mx-auto h-10 w-10 rounded-full flex items-center justify-center bg-white mb-4"
|
||||||
|
>
|
||||||
|
<Icon className="w-6 h-6 text-warning" icon="heroicons:chart-pie" />
|
||||||
|
</div>
|
||||||
|
<div className="block text-sm text-default-600 font-medium mb-1.5">Completed</div>
|
||||||
|
<div className="text-2xl text-default-900 font-medium">45</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<div className="lg:col-span-1 col-span-2">
|
||||||
|
<Card className="bg-primary/20 shadow-none border-none">
|
||||||
|
<CardContent className=" p-4 text-center">
|
||||||
|
<div
|
||||||
|
className="mx-auto h-10 w-10 rounded-full flex items-center justify-center bg-white mb-4"
|
||||||
|
>
|
||||||
|
<Icon className="w-6 h-6 text-primary" icon="heroicons:clock" />
|
||||||
|
</div>
|
||||||
|
<div className="block text-sm text-default-600 font-medium mb-1.5">Hours</div>
|
||||||
|
<div className="text-2xl text-default-900 font-medium">190</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<div className="lg:col-span-1 col-span-2">
|
||||||
|
<Card className="bg-success/20 shadow-none border-none">
|
||||||
|
<CardContent className="p-4 text-center">
|
||||||
|
<div
|
||||||
|
className="mx-auto h-10 w-10 rounded-full flex items-center justify-center bg-white mb-4"
|
||||||
|
>
|
||||||
|
<Icon className="w-6 h-6 text-success" icon="heroicons:calculator" />
|
||||||
|
</div>
|
||||||
|
<div className="block text-sm text-default-600 font-medium mb-1.5">Spendings</div>
|
||||||
|
<div className="text-2xl text-default-900 font-medium">$3,564</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2">
|
||||||
|
<ProgressBlock
|
||||||
|
title="Progress"
|
||||||
|
height={183}
|
||||||
|
className="border-none shadow-none"
|
||||||
|
chartType="pie"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="col-span-12 xl:col-span-5">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>About Project</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
|
||||||
|
<div className="text-base font-medium text-default-800 mb-3">
|
||||||
|
Background information
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-default-600">
|
||||||
|
The Optimistic Website Company - Amet minim mollit non deserunt
|
||||||
|
ullamco est sit aliqua dolor do amet sint. Velit officia consequat
|
||||||
|
duis enim velit mollit. Exercita -tion veniam consequat sunt
|
||||||
|
nostrud amet.
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
<p className="text-sm text-default-600">
|
||||||
|
Amet minim mollit non deserunt ullamco est sit aliqua dolor do
|
||||||
|
amet sint.The Optimistic Website Company - Amet minim mollit non
|
||||||
|
deserunt ullamco est sit aliqua dolor do amet sint. Velit officia
|
||||||
|
consequat duis enim velit mollit. Exercita -tion veniam consequat
|
||||||
|
sunt nostrud amet.
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-default-600 mt-4">
|
||||||
|
Amet minim mollit non deserunt ullamco est sit aliqua dolor do
|
||||||
|
amet sint.The Optimistic Website Company.
|
||||||
|
Amet minim mollit non deserunt ullamco est sit aliqua dolor do
|
||||||
|
amet sint.The Optimistic Website Company. <br /> <br />
|
||||||
|
Amet minim mollit non deserunt ullamco est sit aliqua dolor do
|
||||||
|
amet sint.The Optimistic Website Company.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap mt-8">
|
||||||
|
<div className="xl:mr-8 mr-4 mb-3 space-y-1">
|
||||||
|
<div className="font-semibold text-default-500 ">
|
||||||
|
Existing website
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 text-xs font-normal text-primary">
|
||||||
|
<Icon icon="heroicons:link" />
|
||||||
|
<Link href="#">www.example.com</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="xl:me-8 me-4 mb-3 space-y-1">
|
||||||
|
<div className="font-semibold text-default-500">
|
||||||
|
Project brief
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 text-xs font-normal text-primary-600 ">
|
||||||
|
<Icon icon="heroicons:link" />
|
||||||
|
<Link href="#">www.example.com</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-default-100 rounded px-4 pt-4 pb-1 flex flex-wrap justify-between mt-6">
|
||||||
|
<div className="me-3 mb-3 space-y-2">
|
||||||
|
<div className="text-xs font-medium text-default-600">
|
||||||
|
Project owner
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-default-600">
|
||||||
|
John Doe
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="me-3 mb-3 space-y-2">
|
||||||
|
<div className="text-xs font-medium text-default-600">
|
||||||
|
Budget
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-default-600 ">
|
||||||
|
$75,800
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="me-3 mb-3 space-y-2">
|
||||||
|
<div className="text-xs font-medium text-default-600">
|
||||||
|
Start date
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-default-600">
|
||||||
|
01/11/2021
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="me-3 mb-3 space-y-2">
|
||||||
|
<div className="text-xs font-medium text-default-600 ">
|
||||||
|
Deadline
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-warning">01/11/2021</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="col-span-12 xl:col-span-4">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Notes</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="px-2">
|
||||||
|
<NotesCalendar />
|
||||||
|
<ul className="divide-y divide-default-100">
|
||||||
|
{meets.map((item, i) => (
|
||||||
|
<li key={i} className=" py-2.5 px-3">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex-1 flex gap-2.5">
|
||||||
|
<div className="flex-none">
|
||||||
|
<div className="h-8 w-8">
|
||||||
|
<Image
|
||||||
|
src={item.img}
|
||||||
|
alt=""
|
||||||
|
className="w-full h-full "
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="text-default-600 text-sm mb-1 font-medium">
|
||||||
|
{item.title}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-1 font-normal text-xs text-default-500">
|
||||||
|
<div className="text-base">
|
||||||
|
<Icon icon="heroicons-outline:video-camera" />
|
||||||
|
</div>
|
||||||
|
{item.meet}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-none text-xs text-default-600">
|
||||||
|
{item.date}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-5">
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex-row items-center justify-between">
|
||||||
|
<CardTitle>Task List</CardTitle>
|
||||||
|
<DashboardDropdown />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<ul className="divide-y divide-default-100">
|
||||||
|
{
|
||||||
|
tasks.map((task, index) => <TaskItem
|
||||||
|
key={index}
|
||||||
|
task={task} />)
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex-row items-center justify-between">
|
||||||
|
<CardTitle>Messages</CardTitle>
|
||||||
|
<DashboardDropdown />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<ul className="divide-y divide-default-100">
|
||||||
|
{
|
||||||
|
messagesData.map((message, index) =>
|
||||||
|
<MessageListItem
|
||||||
|
message={message}
|
||||||
|
key={index} />)
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex-row items-center justify-between">
|
||||||
|
<CardTitle>Activity</CardTitle>
|
||||||
|
<DashboardDropdown />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<ul className="relative before:absolute before:start-6 before:top-3.5 before:w-[1px] before:h-[80%] before:bg-default-200">
|
||||||
|
{
|
||||||
|
activityList.map((activity, index) =>
|
||||||
|
<ActivityItem
|
||||||
|
activity={activity}
|
||||||
|
key={index}
|
||||||
|
/>)
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-12 gap-5">
|
||||||
|
<div className="col-span-12 lg:col-span-8">
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex-row items-center justify-between">
|
||||||
|
<CardTitle>Team Members</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<TeamTable data={teamData} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-12 lg:col-span-4">
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex-row items-center justify-between">
|
||||||
|
<CardTitle>Files</CardTitle>
|
||||||
|
<DashboardDropdown />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<ul className="divide-y divide-default-100">
|
||||||
|
{files.map((item, i) => (
|
||||||
|
<li key={i} className="py-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="flex-1 flex gap-2">
|
||||||
|
<div className="flex-none">
|
||||||
|
<div className="h-8 w-8">
|
||||||
|
<Image
|
||||||
|
src={item.img}
|
||||||
|
alt=""
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
className=" w-full h-full object-cover rounded-full border hover:border-white border-transparent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="text-default-600 text-sm">
|
||||||
|
{item.title}
|
||||||
|
</div>
|
||||||
|
<div className="font-normal text-xs text-default-500 mt-1">
|
||||||
|
{item.date}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-none">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="text-xs text-slate-900 dark:text-white"
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SinglePage
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
"use client";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { CalendarIcon } from "lucide-react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm, SubmitHandler, Controller } from "react-hook-form"
|
||||||
|
import Select, { MultiValue } from 'react-select'
|
||||||
|
interface CreateTaskProps {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
interface Option {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
image?: string;
|
||||||
|
}
|
||||||
|
type Inputs = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tags: MultiValue<Option>;
|
||||||
|
assignee: MultiValue<Option>;
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date;
|
||||||
|
}
|
||||||
|
const options: Option[] = [
|
||||||
|
{
|
||||||
|
value: "team",
|
||||||
|
label: "team",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "low",
|
||||||
|
label: "low",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "medium",
|
||||||
|
label: "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "high",
|
||||||
|
label: "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "update",
|
||||||
|
label: "update",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const assigneeOptions: Option[] = [
|
||||||
|
{ value: "mahedi", label: "Mahedi Amin", image: "/images/avatar/av-1.svg" },
|
||||||
|
{ value: "sovo", label: "Sovo Haldar", image: "/images/avatar/av-2.svg" },
|
||||||
|
{ value: "rakibul", label: "Rakibul Islam", image: "/images/avatar/av-3.svg" },
|
||||||
|
{ value: "pritom", label: "Pritom Miha", image: "/images/avatar/av-4.svg" },
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const CreateProject = ({ open, setOpen }: CreateTaskProps) => {
|
||||||
|
const [startDate, setStartDate] = useState<Date>(new Date());
|
||||||
|
const [endDate, setEndtDate] = useState<Date>(new Date());
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
control,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<Inputs>()
|
||||||
|
const onSubmit: SubmitHandler<Inputs> = (data) => {
|
||||||
|
console.log(data)
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create Project</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="projectName">Project Name</Label>
|
||||||
|
<Input
|
||||||
|
id="projectName"
|
||||||
|
placeholder="Project Name"
|
||||||
|
{...register("title", { required: "Title is required." })}
|
||||||
|
color={errors.title ? "destructive" : "default"}
|
||||||
|
/>
|
||||||
|
{errors.title && <p className="text-destructive text-sm font-medium">{errors.title.message}</p>}
|
||||||
|
</div>
|
||||||
|
<div className="grid lg:grid-cols-2 grid-cols-1 gap-5">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="startDate">Start Date</Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full justify-start text-start font-normal border border-default-200 text-default-700 md:px-2.5 hover:bg-transparent hover:text-default-700"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<CalendarIcon className="me-2 h-3.5 w-3.5 text-default-500" />
|
||||||
|
{format(startDate, "PPP")}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<Controller
|
||||||
|
name="startDate"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={startDate}
|
||||||
|
onSelect={(date) => {
|
||||||
|
field.onChange(date);
|
||||||
|
setStartDate(date as Date);
|
||||||
|
}}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="endDate">End Date</Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full justify-start text-start font-normal border border-default-200 text-default-700 md:px-2.5 hover:bg-transparent hover:text-default-700"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<CalendarIcon className="me-2 h-3.5 w-3.5 text-default-500" />
|
||||||
|
{format(endDate, "PPP")}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<Controller
|
||||||
|
name="startDate"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={endDate}
|
||||||
|
onSelect={(date) => {
|
||||||
|
field.onChange(date);
|
||||||
|
setEndtDate(date as Date);
|
||||||
|
}}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="assignee">Assignee</Label>
|
||||||
|
<Controller
|
||||||
|
name="assignee"
|
||||||
|
control={control}
|
||||||
|
defaultValue={[]}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
{...field}
|
||||||
|
options={assigneeOptions}
|
||||||
|
isMulti
|
||||||
|
onChange={(selectedOption) => field.onChange(selectedOption)}
|
||||||
|
getOptionLabel={(option) => (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Image width={40} height={40} src={option.image as string} alt={option.label} className="w-8 h-8 rounded-full me-2" />
|
||||||
|
<span className="text-sm text-default-600 font-medium">{option.label}</span>
|
||||||
|
</div>
|
||||||
|
) as unknown as string}
|
||||||
|
placeholder="Select assignee"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="tag">Tags</Label>
|
||||||
|
<Controller
|
||||||
|
name="tags"
|
||||||
|
control={control}
|
||||||
|
defaultValue={[]}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
{...field}
|
||||||
|
options={options}
|
||||||
|
isMulti
|
||||||
|
onChange={(selectedOption) => field.onChange(selectedOption)}
|
||||||
|
placeholder="Select tags"
|
||||||
|
getOptionLabel={(option) => <span className="text-sm text-default-600 font-medium capitalize">{option.label}</span> as unknown as string}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="description">Description</Label>
|
||||||
|
<Textarea
|
||||||
|
id="description"
|
||||||
|
placeholder="Project Description"
|
||||||
|
{...register("description")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button type="submit">Add</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateProject;
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
|
||||||
|
|
||||||
|
export const defaultProjects = [
|
||||||
|
{
|
||||||
|
id: "c06d48bf-7f35-4789-b71e-d80fee5b430f",
|
||||||
|
title: "CRM Dashboard ",
|
||||||
|
projectLogo: "/images/project/p-2.png",
|
||||||
|
desc: "Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint.",
|
||||||
|
startDate: "2022-10-03",
|
||||||
|
endDate: "2022-10-06",
|
||||||
|
progress: 90,
|
||||||
|
assignee: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-1.svg",
|
||||||
|
name: "Mahedi Amin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-2.svg",
|
||||||
|
name: "Sovo Haldar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-3.svg",
|
||||||
|
name: "Rakibul Islam",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
remainingDays: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Business Dashboard ",
|
||||||
|
projectLogo: "/images/project/p-2.png",
|
||||||
|
desc: "Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint.",
|
||||||
|
startDate: "2022-10-03",
|
||||||
|
endDate: "2022-10-06",
|
||||||
|
progress: 90,
|
||||||
|
assignee: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-1.svg",
|
||||||
|
name: "Mahedi Amin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-2.svg",
|
||||||
|
name: "Sovo Haldar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-3.svg",
|
||||||
|
name: "Rakibul Islam",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
remainingDays: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Management Dashboard ",
|
||||||
|
projectLogo: "/images/project/p-2.png",
|
||||||
|
desc: "Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint.",
|
||||||
|
startDate: "2022-10-03",
|
||||||
|
endDate: "2022-10-06",
|
||||||
|
progress: 90,
|
||||||
|
assignee: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-1.svg",
|
||||||
|
name: "Mahedi Amin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-2.svg",
|
||||||
|
name: "Sovo Haldar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-3.svg",
|
||||||
|
name: "Rakibul Islam",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
remainingDays: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Analytics Dashboard ",
|
||||||
|
projectLogo: "/images/project/p-2.png",
|
||||||
|
desc: "Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint.",
|
||||||
|
startDate: "2022-10-03",
|
||||||
|
endDate: "2022-10-06",
|
||||||
|
progress: 90,
|
||||||
|
assignee: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-1.svg",
|
||||||
|
name: "Mahedi Amin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-2.svg",
|
||||||
|
name: "Sovo Haldar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-3.svg",
|
||||||
|
name: "Rakibul Islam",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
remainingDays: 3
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Marketing Dashboard ",
|
||||||
|
projectLogo: "/images/project/p-2.png",
|
||||||
|
desc: "Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint.",
|
||||||
|
startDate: "2022-10-03",
|
||||||
|
endDate: "2022-10-06",
|
||||||
|
progress: 90,
|
||||||
|
assignee: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-1.svg",
|
||||||
|
name: "Mahedi Amin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-2.svg",
|
||||||
|
name: "Sovo Haldar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/av-3.svg",
|
||||||
|
name: "Rakibul Islam",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
remainingDays: 3
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
export const getProjects = async () => {
|
||||||
|
return defaultProjects
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getProjectById = async (id: string) => {
|
||||||
|
return defaultProjects.find(project => project.id === id)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProjectNav {
|
||||||
|
label: string
|
||||||
|
href: string
|
||||||
|
active: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getProjectNav(pathname: string): ProjectNav[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'grid view',
|
||||||
|
href: "/app/projects/grid",
|
||||||
|
active: pathname === "/app/projects/grid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'list view',
|
||||||
|
href: "/app/projects/list",
|
||||||
|
active: pathname === "/app/projects/list",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Project = (typeof defaultProjects)[number]
|
||||||
|
|
@ -0,0 +1,226 @@
|
||||||
|
"use client";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { CalendarIcon } from "lucide-react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm, SubmitHandler, Controller } from "react-hook-form"
|
||||||
|
import Select, { MultiValue } from 'react-select'
|
||||||
|
interface EditProjectProps {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
interface Option {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
image?: string;
|
||||||
|
}
|
||||||
|
type Inputs = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tags: MultiValue<Option>;
|
||||||
|
assignee: MultiValue<Option>;
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date;
|
||||||
|
}
|
||||||
|
const options: Option[] = [
|
||||||
|
{
|
||||||
|
value: "team",
|
||||||
|
label: "team",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "low",
|
||||||
|
label: "low",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "medium",
|
||||||
|
label: "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "high",
|
||||||
|
label: "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "update",
|
||||||
|
label: "update",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const assigneeOptions: Option[] = [
|
||||||
|
{ value: "mahedi", label: "Mahedi Amin", image: "/images/avatar/av-1.svg" },
|
||||||
|
{ value: "sovo", label: "Sovo Haldar", image: "/images/avatar/av-2.svg" },
|
||||||
|
{ value: "rakibul", label: "Rakibul Islam", image: "/images/avatar/av-3.svg" },
|
||||||
|
{ value: "pritom", label: "Pritom Miha", image: "/images/avatar/av-4.svg" },
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const EditProject = ({ open, setOpen }: EditProjectProps) => {
|
||||||
|
const [startDate, setStartDate] = useState<Date>(new Date());
|
||||||
|
const [endDate, setEndtDate] = useState<Date>(new Date());
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
control,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<Inputs>()
|
||||||
|
const onSubmit: SubmitHandler<Inputs> = (data) => {
|
||||||
|
console.log(data)
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create Task</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="projectName">Project Name</Label>
|
||||||
|
<Input
|
||||||
|
id="projectName"
|
||||||
|
placeholder="Project Name"
|
||||||
|
defaultValue="Project Title"
|
||||||
|
{...register("title", { required: "Title is required." })}
|
||||||
|
color={errors.title ? "destructive" : "default"}
|
||||||
|
/>
|
||||||
|
{errors.title && <p className="text-destructive text-sm font-medium">{errors.title.message}</p>}
|
||||||
|
</div>
|
||||||
|
<div className="grid lg:grid-cols-2 grid-cols-1 gap-5">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="startDate">Start Date</Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full justify-start text-start font-normal border border-default-200 text-default-700 md:px-2.5 hover:bg-transparent hover:text-default-700"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<CalendarIcon className="me-2 h-3.5 w-3.5 text-default-500" />
|
||||||
|
{format(startDate, "PPP")}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<Controller
|
||||||
|
name="startDate"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={startDate}
|
||||||
|
onSelect={(date) => {
|
||||||
|
field.onChange(date);
|
||||||
|
setStartDate(date as Date);
|
||||||
|
}}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="endDate">End Date</Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full justify-start text-start font-normal border border-default-200 text-default-700 md:px-2.5 hover:bg-transparent hover:text-default-700"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<CalendarIcon className="me-2 h-3.5 w-3.5 text-default-500" />
|
||||||
|
{format(endDate, "PPP")}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<Controller
|
||||||
|
name="startDate"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={endDate}
|
||||||
|
onSelect={(date) => {
|
||||||
|
field.onChange(date);
|
||||||
|
setEndtDate(date as Date);
|
||||||
|
}}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="assignee">Assignee</Label>
|
||||||
|
<Controller
|
||||||
|
name="assignee"
|
||||||
|
control={control}
|
||||||
|
defaultValue={[{ value: "mahedi", label: "Mahedi Amin", image: "/images/avatar/av-1.svg" }]}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
{...field}
|
||||||
|
options={assigneeOptions}
|
||||||
|
isMulti
|
||||||
|
onChange={(selectedOption) => field.onChange(selectedOption)}
|
||||||
|
getOptionLabel={(option) => (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Image width={40} height={40} src={option.image as string} alt={option.label} className="w-8 h-8 rounded-full me-2" />
|
||||||
|
<span className="text-sm text-default-600 font-medium">{option.label}</span>
|
||||||
|
</div>
|
||||||
|
) as unknown as string}
|
||||||
|
placeholder="Select assignee"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="tag">Tags</Label>
|
||||||
|
<Controller
|
||||||
|
name="tags"
|
||||||
|
control={control}
|
||||||
|
defaultValue={[{ value: "team", label: "Team" }]}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
{...field}
|
||||||
|
options={options}
|
||||||
|
isMulti
|
||||||
|
onChange={(selectedOption) => field.onChange(selectedOption)}
|
||||||
|
placeholder="Select tags"
|
||||||
|
getOptionLabel={(option) => <span className="text-sm text-default-600 font-medium capitalize">{option.label}</span> as unknown as string}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="description">Description</Label>
|
||||||
|
<Textarea
|
||||||
|
id="description"
|
||||||
|
placeholder="Project Description"
|
||||||
|
{...register("description")}
|
||||||
|
defaultValue="Project Description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button type="submit">Add</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditProject;
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
import { Rabbit } from 'lucide-react';
|
||||||
|
|
||||||
|
|
||||||
|
const EmptyProject = () => {
|
||||||
|
return (
|
||||||
|
<div className="">
|
||||||
|
<Rabbit className=" h-[200px] w-[200px] text-default-400 " strokeWidth={1} />
|
||||||
|
<div className=' text-sm text-default-500 text-center'>No tasks</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmptyProject;
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
'use client'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import EditProject from "../../edit-project";
|
||||||
|
import DeleteConfirmationDialog from "@/components/delete-confirmation-dialog";
|
||||||
|
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu"
|
||||||
|
import { defaultProjects } from '../../data';
|
||||||
|
import { Link } from '@/i18n/routing'; const ProjectAction = () => {
|
||||||
|
const [editTaskOpen, setEditTaskOpen] = useState<boolean>(false);
|
||||||
|
const [deleteProject, setDeleteProject] = useState<boolean>(false)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EditProject
|
||||||
|
open={editTaskOpen}
|
||||||
|
setOpen={setEditTaskOpen}
|
||||||
|
/>
|
||||||
|
<DeleteConfirmationDialog
|
||||||
|
open={deleteProject}
|
||||||
|
onClose={() => setDeleteProject(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="flex-none bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent w-6"
|
||||||
|
>
|
||||||
|
<MoreVertical className="h-4 w-4 text-default-700" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="p-0 overflow-hidden" align="end" >
|
||||||
|
<DropdownMenuItem
|
||||||
|
|
||||||
|
className="py-2 border-b border-default-200 text-default-600 focus:bg-default focus:text-default-foreground rounded-none cursor-pointer"
|
||||||
|
|
||||||
|
>
|
||||||
|
<Link href={`/app/projects/${defaultProjects[0].id}`} className=' flex items-center w-full'>
|
||||||
|
<Eye className="w-3.5 h-3.5 me-1" />
|
||||||
|
View
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => setEditTaskOpen(true)}
|
||||||
|
className="py-2 border-b border-default-200 text-default-600 focus:bg-default focus:text-default-foreground rounded-none cursor-pointer"
|
||||||
|
|
||||||
|
>
|
||||||
|
<SquarePen className="w-3.5 h-3.5 me-1" />
|
||||||
|
Edit
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => setDeleteProject(true)}
|
||||||
|
className="py-2 bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none cursor-pointer"
|
||||||
|
|
||||||
|
>
|
||||||
|
<Trash2 className="w-3.5 h-3.5 me-1" />
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectAction
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { getProjects } from '../data'
|
||||||
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import { Progress } from "@/components/ui/progress";
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
import EmptyProject from './components/empty';
|
||||||
|
import ProjectAction from './components/project-action';
|
||||||
|
const ProjectGrid = async () => {
|
||||||
|
const projects = await getProjects()
|
||||||
|
if (projects.length === 0) return <EmptyProject />
|
||||||
|
return (
|
||||||
|
<div className='grid lg:grid-cols-3 md:grid-cols-2 grid-cols-1 gap-5'>
|
||||||
|
{projects?.map(({ projectLogo, title, desc, startDate, endDate, progress, assignee, remainingDays }, index) => (
|
||||||
|
<Card key={index}>
|
||||||
|
<CardHeader className="flex-row gap-1 items-center space-y-0">
|
||||||
|
<div className="flex-1 flex items-center gap-4">
|
||||||
|
<Avatar className="flex-none h-10 w-10 rounded bg-default-200 text-default hover:bg-default-200">
|
||||||
|
<AvatarImage src={projectLogo} />
|
||||||
|
<AvatarFallback className="uppercase"> {title.charAt(0) + title.charAt(1)}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<h3 className="text-default-900 text-lg font-medium max-w-[210px] truncate text-center capitalize ">{title}</h3>
|
||||||
|
</div>
|
||||||
|
<ProjectAction />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="text-default-600 text-sm">{desc}</div>
|
||||||
|
<div className="flex gap-4 mt-6">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-default-400 mb-1">Start Date</div>
|
||||||
|
<div className="text-xs text-default-600 font-medium">{startDate}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-default-400 mb-1">End Date</div>
|
||||||
|
<div className="text-xs text-default-600 font-medium">{endDate}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-1">
|
||||||
|
<div className="text-end text-xs text-default-600 mb-1.5 font-medium">{progress}%</div>
|
||||||
|
<Progress value={progress} color="primary" size="sm" />
|
||||||
|
</div>
|
||||||
|
<div className="flex mt-5">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="text-default-400 text-sm font-normal mb-3">Assigned to</div>
|
||||||
|
<div className="flex items-center -space-x-1">
|
||||||
|
{
|
||||||
|
assignee?.map((user, index) => (
|
||||||
|
<Avatar
|
||||||
|
key={`user-${index}`}
|
||||||
|
className="h-6 w-6 shadow-none border-none bg-transparent hover:bg-transparent"
|
||||||
|
>
|
||||||
|
<AvatarImage src={user.image} />
|
||||||
|
<AvatarFallback> {user.name.charAt(0) + user.name.charAt(1)}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
<div className="bg-card text-default-900 text-xs ring-2 ring-default-100 rounded-full h-6 w-6 flex flex-col justify-center items-center">
|
||||||
|
+2
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-none">
|
||||||
|
<div className="flex items-center gap-1 bg-destructive/10 text-destructive rounded-full px-2 py-0.5 text-xs mt-1">
|
||||||
|
<Icon icon="heroicons-outline:clock" />
|
||||||
|
{remainingDays} days left
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectGrid
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import ProjectWrapper from "./project-wrapper";
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Projects',
|
||||||
|
description: 'Projects Page'
|
||||||
|
}
|
||||||
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<ProjectWrapper>
|
||||||
|
{children}
|
||||||
|
</ProjectWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
|
|
@ -0,0 +1,310 @@
|
||||||
|
"use client"
|
||||||
|
import * as React from "react"
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useReactTable
|
||||||
|
} from "@tanstack/react-table"
|
||||||
|
import { defaultProjects, type Project } from "../../data";
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { ChevronLeft, ChevronRight, Eye, MoreVertical, SquarePen, Trash2 } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table"
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import { Progress } from "@/components/ui/progress";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu"
|
||||||
|
import { Link } from '@/i18n/routing';
|
||||||
|
import EditProject from "../../edit-project";
|
||||||
|
import DeleteConfirmationDialog from "@/components/delete-confirmation-dialog";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
||||||
|
const ListTable = ({ projects }: { projects: Project[] }) => {
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||||
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
React.useState<VisibilityState>({})
|
||||||
|
const [rowSelection, setRowSelection] = React.useState({})
|
||||||
|
const [editTaskOpen, setEditTaskOpen] = React.useState<boolean>(false);
|
||||||
|
const [deleteProject, setDeleteProject] = React.useState<boolean>(false)
|
||||||
|
|
||||||
|
const columns: ColumnDef<Project>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: "title",
|
||||||
|
header: "Name",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Avatar className="w-10 h-10 shadow-none border-none bg-transparent hover:bg-transparent">
|
||||||
|
<AvatarImage src={row.original.projectLogo} />
|
||||||
|
<AvatarFallback> DC</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="font-medium text-sm leading-4 whitespace-nowrap">
|
||||||
|
{row.getValue("title")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "startDate",
|
||||||
|
header: "Start Date",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<span className="whitespace-nowrap">{row.getValue("startDate")}</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "endDate",
|
||||||
|
header: "End Date",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<span className="whitespace-nowrap">{row.getValue("endDate")}</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "assignee",
|
||||||
|
header: "Assignee",
|
||||||
|
cell: ({ row }: { row: { getValue: (key: string) => { image: string }[] } }) => {
|
||||||
|
const assignees = row.getValue("assignee");
|
||||||
|
return (
|
||||||
|
<div className="flex -space-x-1 rtl:space-x-reverse">
|
||||||
|
<div className="flex -space-x-1 rtl:space-x-reverse">
|
||||||
|
{assignees.map((item, index) => (
|
||||||
|
<Avatar
|
||||||
|
key={`user-${index}`}
|
||||||
|
className="h-6 w-6 shadow-none border-none bg-transparent hover:bg-transparent"
|
||||||
|
>
|
||||||
|
<AvatarImage src={item.image} />
|
||||||
|
<AvatarFallback>SC</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="bg-card text-default-900 text-xs ring-2 ring-default-100 rounded-full h-6 w-6 flex flex-col justify-center items-center">
|
||||||
|
+2
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "progress",
|
||||||
|
header: "Status",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Progress value={row.getValue("progress")} color="primary" size="sm" />
|
||||||
|
<div className=" text-xs text-default-500 font-medium mt-3 whitespace-nowrap">
|
||||||
|
12/15 Task Completed
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
accessorKey: "action",
|
||||||
|
header: "Action",
|
||||||
|
enableHiding: false,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="flex-none ring-offset-transparent bg-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent w-6"
|
||||||
|
>
|
||||||
|
<MoreVertical className="h-4 w-4 text-default-700" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="p-0 overflow-hidden" align="end" >
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="py-2 border-b border-default-200 text-default-600 focus:bg-default focus:text-default-foreground rounded-none cursor-pointer"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<Link href={`/app/projects/${defaultProjects[0].id}`}>
|
||||||
|
<Eye className="w-3.5 h-3.5 me-1" />
|
||||||
|
View
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="py-2 border-b border-default-200 text-default-600 focus:bg-default focus:text-default-foreground rounded-none cursor-pointer"
|
||||||
|
onClick={() => setEditTaskOpen(true)}
|
||||||
|
>
|
||||||
|
<SquarePen className="w-3.5 h-3.5 me-1" />
|
||||||
|
Edit
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="py-2 bg-destructive/10 text-destructive focus:bg-destructive focus:text-destructive-foreground rounded-none cursor-pointer"
|
||||||
|
onClick={() => setDeleteProject(true)}
|
||||||
|
>
|
||||||
|
<Trash2 className="w-3.5 h-3.5 me-1" />
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const table = useReactTable({
|
||||||
|
data: projects,
|
||||||
|
columns,
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
onColumnFiltersChange: setColumnFilters,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
columnFilters,
|
||||||
|
columnVisibility,
|
||||||
|
rowSelection
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EditProject
|
||||||
|
open={editTaskOpen}
|
||||||
|
setOpen={setEditTaskOpen}
|
||||||
|
/>
|
||||||
|
<DeleteConfirmationDialog
|
||||||
|
open={deleteProject}
|
||||||
|
onClose={() => setDeleteProject(false)}
|
||||||
|
/>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Project List</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<Table>
|
||||||
|
<TableHeader className="px-3 bg-default-100">
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header) => {
|
||||||
|
return (
|
||||||
|
<TableHead key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{table.getRowModel().rows?.length ? (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<TableRow
|
||||||
|
key={row.id}
|
||||||
|
data-state={row.getIsSelected() && "selected"}
|
||||||
|
className="even:bg-default-100 px-6 h-20" >
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(
|
||||||
|
cell.column.columnDef.cell,
|
||||||
|
cell.getContext()
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
colSpan={columns.length}
|
||||||
|
className="h-24 text-center"
|
||||||
|
>
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<div className="flex items-center justify-end py-4 px-10">
|
||||||
|
|
||||||
|
<div className="flex-1 flex items-center gap-3">
|
||||||
|
<div className=" flex gap-2 items-center">
|
||||||
|
<div className="text-sm font-medium text-default-60">Go </div>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
className="w-16 px-2"
|
||||||
|
defaultValue={table.getState().pagination.pageIndex + 1}
|
||||||
|
onChange={(e) => {
|
||||||
|
const pageNumber = e.target.value ? Number(e.target.value) - 1 : 0;
|
||||||
|
table.setPageIndex(pageNumber);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm font-medium text-default-600">
|
||||||
|
Page{" "} {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 flex-none">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
className='w-8 h-8'
|
||||||
|
>
|
||||||
|
<ChevronLeft className='w-4 h-4' />
|
||||||
|
</Button>
|
||||||
|
{table.getPageOptions().map((page, pageIndex) => (
|
||||||
|
<Button
|
||||||
|
key={`basic-data-table-${pageIndex}`}
|
||||||
|
onClick={() => table.setPageIndex(pageIndex)}
|
||||||
|
size="icon"
|
||||||
|
className={`w-8 h-8 ${table.getState().pagination.pageIndex === pageIndex ? 'bg-default' : 'bg-default-300 text-default'}`}
|
||||||
|
>
|
||||||
|
{page + 1}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
className='w-8 h-8'
|
||||||
|
>
|
||||||
|
<ChevronRight className='w-4 h-4' />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default ListTable;
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react'
|
||||||
|
import ListTable from './components/list-table'
|
||||||
|
import { getProjects } from '../data'
|
||||||
|
|
||||||
|
const ProjectList = async () => {
|
||||||
|
const projects = await getProjects()
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ListTable projects={projects} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectList
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const Loading = () => {
|
||||||
|
return (
|
||||||
|
<div>loading..</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Loading
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
import { redirect } from '@/components/navigation'
|
||||||
|
|
||||||
|
const ProjectPage = () => {
|
||||||
|
redirect('/app/projects/grid')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectPage
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
"use client"
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Filter, List, Plus } from "lucide-react";
|
||||||
|
import CreateProject from "./create-project";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Link, usePathname } from "@/components/navigation";
|
||||||
|
import { getProjectNav } from "./data";
|
||||||
|
|
||||||
|
const ProjectWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
const pathname = usePathname();
|
||||||
|
const menus = getProjectNav(pathname)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<CreateProject
|
||||||
|
open={open}
|
||||||
|
setOpen={setOpen}
|
||||||
|
/>
|
||||||
|
<div className="flex w-full flex-wrap items-center gap-4 mb-6">
|
||||||
|
<h4 className="flex-1 font-medium lg:text-2xl text-xl capitalize text-default-900">
|
||||||
|
Project
|
||||||
|
</h4>
|
||||||
|
<div className="flex items-center gap-4 flex-wrap">
|
||||||
|
{menus?.map(({ label, href, active }, index) => (
|
||||||
|
<Button
|
||||||
|
key={index}
|
||||||
|
asChild
|
||||||
|
className={cn("flex-none capitalize bg-card text-default-600 hover:bg-card hover:text-default-600 hover:ring-0 hover:ring-transparent", {
|
||||||
|
"bg-default text-default-foreground hover:bg-default hover:text-default-foreground": active,
|
||||||
|
})}
|
||||||
|
|
||||||
|
>
|
||||||
|
<Link href={href}>
|
||||||
|
<List className="w-3.5 h-3.5 me-1" />
|
||||||
|
<span>{label}</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
|
||||||
|
|
||||||
|
<Button className="flex-none bg-card text-default-600 hover:ring-0 hover:ring-transparent hover:bg-default hover:text-default-foreground" >
|
||||||
|
<Filter className="w-3.5 h-3.5 me-1" />
|
||||||
|
<span>On Going</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="flex-none"
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4 me-1" />
|
||||||
|
<span>Add Project</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProjectWrapper;
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
"use client"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
|
import { CalendarIcon, Plus } from "lucide-react";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { toast } from "@/components/ui/use-toast"
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
const assigneeOptions = [
|
||||||
|
{ value: "mahedi", label: "Mahedi Amin", image: "/images/avatar/av-1.svg" },
|
||||||
|
{ value: "sovo", label: "Sovo Haldar", image: "/images/avatar/av-2.svg" },
|
||||||
|
{ value: "rakibul", label: "Rakibul Islam", image: "/images/avatar/av-3.svg" },
|
||||||
|
{ value: "pritom", label: "Pritom Miha", image: "/images/avatar/av-4.svg" },
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const FormSchema = z.object({
|
||||||
|
title: z.string().min(2, {
|
||||||
|
message: "Title must be at least 2 characters.",
|
||||||
|
}),
|
||||||
|
assign: z.string().optional(),
|
||||||
|
tag: z.string().optional(),
|
||||||
|
dob: z.date().optional(),
|
||||||
|
description: z.string().optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const CreateTodo = () => {
|
||||||
|
const t = useTranslations("TodoApp")
|
||||||
|
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
|
||||||
|
const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
|
resolver: zodResolver(FormSchema),
|
||||||
|
defaultValues: {
|
||||||
|
title: "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||||
|
toast({
|
||||||
|
title: "Task created successfully.",
|
||||||
|
})
|
||||||
|
setIsDialogOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button fullWidth size="lg" className="dark:bg-background dark:text-foreground">
|
||||||
|
<Plus className="w-6 h-6 me-1.5" />
|
||||||
|
{t("addTask")}
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader className="mb-4">
|
||||||
|
<DialogTitle> {t("addTask")}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="title"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="text-default-700">Title</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Enter Title" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="assign"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="text-default-700">Assign</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select..." />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{
|
||||||
|
assigneeOptions.map((user, index) =>
|
||||||
|
<SelectItem
|
||||||
|
key={`user-${index}`}
|
||||||
|
value={user.value}>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Image
|
||||||
|
src={user.image}
|
||||||
|
alt={user.label}
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className="w-5 h-5 rounded-full" />
|
||||||
|
<span className="text-sm text-default-900">{user.label}</span>
|
||||||
|
</div>
|
||||||
|
</SelectItem>)
|
||||||
|
}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="dob"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-col">
|
||||||
|
<FormLabel className="text-default-700">Due Date</FormLabel>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<FormControl>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="md"
|
||||||
|
fullWidth
|
||||||
|
className={cn(
|
||||||
|
"border-default md:px-3",
|
||||||
|
!field.value && "text-muted-foreground border-default-200 md:px-3"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{field.value ? (
|
||||||
|
format(field.value, "PPP")
|
||||||
|
) : (
|
||||||
|
<span>Pick a date</span>
|
||||||
|
)}
|
||||||
|
<CalendarIcon className={cn("ms-auto h-4 w-4 text-default-300",
|
||||||
|
{ "text-default-900": field.value }
|
||||||
|
)} />
|
||||||
|
</Button>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0" align="start">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={field.value}
|
||||||
|
onSelect={field.onChange}
|
||||||
|
disabled={(date) =>
|
||||||
|
date > new Date() || date < new Date("1900-01-01")
|
||||||
|
}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="tag"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="text-default-700">Tag</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select..." />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="team">Team</SelectItem>
|
||||||
|
<SelectItem value="low">Low</SelectItem>
|
||||||
|
<SelectItem value="medium">Medium</SelectItem>
|
||||||
|
<SelectItem value="high">High</SelectItem>
|
||||||
|
<SelectItem value="update">Update</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="text-default-700">Description</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
placeholder="Tell us a little bit about yourself"
|
||||||
|
className="resize-none"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button type="submit">Submit</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateTodo;
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
|
||||||
|
export const todos = [
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
image: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-4.png",
|
||||||
|
label: "Mahedi Amin",
|
||||||
|
value: "mahedi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-2.png",
|
||||||
|
label: "Sovo Haldar",
|
||||||
|
value: "sovo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-3.png",
|
||||||
|
label: "Rakibul Islam",
|
||||||
|
value: "rakibul",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: "laboriosam mollitia et enim quasi adipisci quia provident illum",
|
||||||
|
isDone: false,
|
||||||
|
isfav: false,
|
||||||
|
isTrash: false,
|
||||||
|
category: [
|
||||||
|
{
|
||||||
|
value: "team",
|
||||||
|
label: "team",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
image: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-2.png",
|
||||||
|
label: "Rakibul Islam",
|
||||||
|
value: "rakibul",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
title:
|
||||||
|
"Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint.",
|
||||||
|
isDone: false,
|
||||||
|
isfav: true,
|
||||||
|
isTrash: false,
|
||||||
|
category: [
|
||||||
|
{
|
||||||
|
value: "low",
|
||||||
|
label: "low",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
image: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-4.png",
|
||||||
|
label: "Sovo Haldar",
|
||||||
|
value: "sovo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-1.png",
|
||||||
|
label: "Rakibul Islam",
|
||||||
|
value: "rakibul",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title:
|
||||||
|
"Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint.",
|
||||||
|
isDone: true,
|
||||||
|
isfav: true,
|
||||||
|
isTrash: false,
|
||||||
|
category: [
|
||||||
|
{
|
||||||
|
value: "medium",
|
||||||
|
label: "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "low",
|
||||||
|
label: "low",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id:faker.string.uuid(),
|
||||||
|
image: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-3.png",
|
||||||
|
label: "Mahedi Amin",
|
||||||
|
value: "mahedi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-4.png",
|
||||||
|
label: "Sovo Haldar",
|
||||||
|
value: "sovo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-4.png",
|
||||||
|
label: "Rakibul Islam",
|
||||||
|
value: "rakibul",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
title: "illo expedita consequatur quia in",
|
||||||
|
isDone: false,
|
||||||
|
isfav: false,
|
||||||
|
isTrash: false,
|
||||||
|
category: [
|
||||||
|
{
|
||||||
|
value: "high",
|
||||||
|
label: "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "low",
|
||||||
|
label: "low",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id:faker.string.uuid(),
|
||||||
|
image: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar/avatar-5.png",
|
||||||
|
label: "Rakibul Islam",
|
||||||
|
value: "rakibul",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: "illo expedita consequatur quia in",
|
||||||
|
isDone: false,
|
||||||
|
isfav: false,
|
||||||
|
isTrash: false,
|
||||||
|
category: [
|
||||||
|
{
|
||||||
|
value: "update",
|
||||||
|
label: "update",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export type Todo = ( typeof todos)[number]
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
"use client"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
|
import { CalendarIcon } from "lucide-react";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { z } from "zod"
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { toast } from "@/components/ui/use-toast"
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
|
const assigneeOptions = [
|
||||||
|
{ value: "mahedi", label: "Mahedi Amin", image: "/images/avatar/av-1.svg" },
|
||||||
|
{ value: "sovo", label: "Sovo Haldar", image: "/images/avatar/av-2.svg" },
|
||||||
|
{ value: "rakibul", label: "Rakibul Islam", image: "/images/avatar/av-3.svg" },
|
||||||
|
{ value: "pritom", label: "Pritom Miha", image: "/images/avatar/av-4.svg" },
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const FormSchema = z.object({
|
||||||
|
title: z.string().min(2, {
|
||||||
|
message: "Title must be at least 2 characters.",
|
||||||
|
}),
|
||||||
|
assign: z.string().optional(),
|
||||||
|
tag: z.string().optional(),
|
||||||
|
dob: z.date().optional(),
|
||||||
|
description: z.string().optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
const EditTodo = ({ open, setOpen }: { open: boolean; setOpen: React.Dispatch<React.SetStateAction<boolean>> }) => {
|
||||||
|
const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
|
resolver: zodResolver(FormSchema),
|
||||||
|
defaultValues: {
|
||||||
|
title: "This is title",
|
||||||
|
assign: "mahedi",
|
||||||
|
tag: "high",
|
||||||
|
dob: new Date(),
|
||||||
|
description: "This is description"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||||
|
toast({
|
||||||
|
title: "Task Update successfully.",
|
||||||
|
})
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen} >
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle> Edit Task</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="title"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="text-default-700">Title</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Enter Title" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="assign"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="text-default-700">Assign</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select..." />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{
|
||||||
|
assigneeOptions.map((user, index) =>
|
||||||
|
<SelectItem
|
||||||
|
key={`user-${index}`}
|
||||||
|
value={user.value}>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Image
|
||||||
|
src={user.image}
|
||||||
|
alt={user.label}
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className="w-5 h-5 rounded-full" />
|
||||||
|
<span className="text-sm text-default-900">{user.label}</span>
|
||||||
|
</div>
|
||||||
|
</SelectItem>)
|
||||||
|
}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="dob"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-col">
|
||||||
|
<FormLabel className="text-default-700">Due Date</FormLabel>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<FormControl>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="md"
|
||||||
|
fullWidth
|
||||||
|
className={cn(
|
||||||
|
"border-default md:px-3",
|
||||||
|
!field.value && "text-muted-foreground border-default-200 md:px-3"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{field.value ? (
|
||||||
|
format(field.value, "PPP")
|
||||||
|
) : (
|
||||||
|
<span>Pick a date</span>
|
||||||
|
)}
|
||||||
|
<CalendarIcon className={cn("ms-auto h-4 w-4 text-default-300",
|
||||||
|
{ "text-default-900": field.value }
|
||||||
|
)} />
|
||||||
|
</Button>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0" align="start">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={field.value}
|
||||||
|
onSelect={field.onChange}
|
||||||
|
disabled={(date) =>
|
||||||
|
date > new Date() || date < new Date("1900-01-01")
|
||||||
|
}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="tag"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="text-default-700">Tag</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select..." />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="team">Team</SelectItem>
|
||||||
|
<SelectItem value="low">Low</SelectItem>
|
||||||
|
<SelectItem value="medium">Medium</SelectItem>
|
||||||
|
<SelectItem value="high">High</SelectItem>
|
||||||
|
<SelectItem value="update">Update</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="text-default-700">Description</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
placeholder="Tell us a little bit about yourself"
|
||||||
|
className="resize-none"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button type="submit">Update</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditTodo;
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import TodoWrapper from "./todo-wrapper";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Todo",
|
||||||
|
description: "Todo Application"
|
||||||
|
}
|
||||||
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<TodoWrapper>
|
||||||
|
{children}
|
||||||
|
</TodoWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
|
import CreateTodo from "./create-todo";
|
||||||
|
import { todos } from "./data";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import TodoHeader from "./todo-header";
|
||||||
|
import Todo from "./todo";
|
||||||
|
import TodoSidebarWrapper from "./sidebar-wrapper";
|
||||||
|
import Nav from "@/components/nav";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
const TodoPage = () => {
|
||||||
|
const t = useTranslations("TodoApp");
|
||||||
|
return (
|
||||||
|
<div className="flex gap-5 h-full ">
|
||||||
|
<TodoSidebarWrapper >
|
||||||
|
<Card className="h-full">
|
||||||
|
<CardContent className="h-full p-0">
|
||||||
|
<ScrollArea className="h-[calc(100%-30px)]" >
|
||||||
|
<div className="mb-4 px-4 pt-5 m-1 sticky top-0">
|
||||||
|
<CreateTodo />
|
||||||
|
</div>
|
||||||
|
<Nav links={[
|
||||||
|
{
|
||||||
|
title: "My Task",
|
||||||
|
icon: "uil:image-v",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
title: "Starred",
|
||||||
|
icon: "heroicons:star",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
title: "Completed",
|
||||||
|
icon: "heroicons:document-check",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
title: "Trash",
|
||||||
|
icon: "heroicons:trash",
|
||||||
|
active: false,
|
||||||
|
}
|
||||||
|
]} />
|
||||||
|
<div className="py-4 px-5 text-default-800 font-semibold text-xs uppercase">
|
||||||
|
{t("todos")}
|
||||||
|
</div>
|
||||||
|
<Nav dotStyle links={[
|
||||||
|
{
|
||||||
|
title: "Team",
|
||||||
|
active: true,
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "low",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "medium",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "high",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "update",
|
||||||
|
active: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
]} />
|
||||||
|
</ScrollArea>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TodoSidebarWrapper>
|
||||||
|
<div className="flex-1 w-full">
|
||||||
|
<Card className="h-full overflow-hidden">
|
||||||
|
<CardHeader className="border-b border-default-200 ">
|
||||||
|
<TodoHeader />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-0 h-full">
|
||||||
|
<div className="h-[calc(100%-60px)] overflow-y-auto no-scrollbar">
|
||||||
|
{
|
||||||
|
todos.map((todo, index) =>
|
||||||
|
<Todo
|
||||||
|
key={`todo-${index}`}
|
||||||
|
todo={todo}
|
||||||
|
/>)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TodoPage;
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
'use client'
|
||||||
|
import { useMediaQuery } from '@/hooks/use-media-query'
|
||||||
|
import { useTodoConfig } from '@/hooks/use-todo'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import React from 'react'
|
||||||
|
const TodoSidebarWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [todoConfig] = useTodoConfig()
|
||||||
|
const { isOpen } = todoConfig
|
||||||
|
const isTablet = useMediaQuery("(min-width: 1024px)");
|
||||||
|
if (!isTablet) {
|
||||||
|
return (
|
||||||
|
<div className={cn('absolute h-full start-0 w-[240px] z-50 ', {
|
||||||
|
'-start-full': !isOpen
|
||||||
|
})}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className='flex-none md:w-[270px] w-[200px] '>{children}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TodoSidebarWrapper
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { MoreVertical, Search } from "lucide-react";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu"
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
import ToggleTodoSidebar from "./toggle-todo-sidebar";
|
||||||
|
|
||||||
|
|
||||||
|
const TodoHeader = () => {
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
name: "Reset Sort",
|
||||||
|
icon: "heroicons-outline:sort-ascending",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sort A-Z ",
|
||||||
|
icon: "heroicons-outline:sort-ascending",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sort Z-A ",
|
||||||
|
icon: "heroicons-outline:sort-descending",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex-1 flex items-center gap-1">
|
||||||
|
<ToggleTodoSidebar />
|
||||||
|
<Search className="w-4 h-4 text-default-400" />
|
||||||
|
<Input placeholder="Search Task" className="max-w-[180px] border-none font-medium dark:bg-transparent" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-none">
|
||||||
|
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button size="icon" className="w-8 h-8 rounded-full bg-default-100 group">
|
||||||
|
<MoreVertical className="w-5 h-5 text-default group-hover:text-default-foreground" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="p-0 rounded-md overflow-hidden">
|
||||||
|
{
|
||||||
|
actions.map((action, index) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={`action-${index}`}
|
||||||
|
className="flex items-center gap-1.5 p-2 border-b text-default-600 group focus:bg-default focus:text-primary-foreground rounded-none group"
|
||||||
|
>
|
||||||
|
<Icon icon={action.icon} className="group-hover:text-primary-foreground w-4 h-4" />
|
||||||
|
<span className="text-default-700 group-hover:text-primary-foreground">{action.name}</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TodoHeader;
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
'use client'
|
||||||
|
import { useMediaQuery } from '@/hooks/use-media-query';
|
||||||
|
import { useTodoConfig } from '@/hooks/use-todo'
|
||||||
|
|
||||||
|
const TodoWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [todoConfig, setTodoConfig] = useTodoConfig();
|
||||||
|
const { isOpen } = todoConfig
|
||||||
|
const isTablet = useMediaQuery("(min-width: 1024px)");
|
||||||
|
return (
|
||||||
|
<div className='relative app-height'>
|
||||||
|
{!isTablet && isOpen && (
|
||||||
|
<div
|
||||||
|
onClick={() => setTodoConfig({ ...todoConfig, isOpen: false })}
|
||||||
|
className="overlay bg-default-900 dark:bg-default-900 dark:bg-opacity-60 bg-opacity-60 backdrop-filter
|
||||||
|
backdrop-blur-sm absolute w-full flex-1 inset-0 z-20 rounded-md"
|
||||||
|
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TodoWrapper
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
"use client"
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import { AvatarFallback } from "@radix-ui/react-avatar";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip"
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import EditTodo from "./edit-todo";
|
||||||
|
import { Todo as TodoProps } from "./data";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { SquarePen, Trash2 } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import DeleteConfirmationDialog from "@/components/delete-confirmation-dialog";
|
||||||
|
|
||||||
|
const Todo = ({ todo }: { todo: TodoProps }) => {
|
||||||
|
const { image, title, isfav, category } = todo;
|
||||||
|
|
||||||
|
const [confirmDelete, setConfirmDelete] = useState<boolean>(false);
|
||||||
|
const [openEdit, setOpenEdit] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const categoryClasses: Record<string, string> = {
|
||||||
|
team: "bg-destructive/10 text-destructive",
|
||||||
|
high: "bg-primary/10 text-primary",
|
||||||
|
medium: "bg-warning/10 text-warning",
|
||||||
|
low: "bg-success/10 text-success",
|
||||||
|
update: "bg-info/10 text-info"
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DeleteConfirmationDialog
|
||||||
|
open={confirmDelete}
|
||||||
|
onClose={() => setConfirmDelete(false)}
|
||||||
|
/>
|
||||||
|
<EditTodo
|
||||||
|
open={openEdit}
|
||||||
|
setOpen={setOpenEdit}
|
||||||
|
/>
|
||||||
|
<div className="flex items-center gap-4 border-b border-defualt-200 dark:border-default-300 last:border-none px-6 py-4 transition-all duration-300 hover:-translate-y-1">
|
||||||
|
<div>
|
||||||
|
<Checkbox className="mt-0.5 dark:bg-default-300"/>
|
||||||
|
</div>
|
||||||
|
<div className="ms-1">
|
||||||
|
{isfav ? (
|
||||||
|
<Icon
|
||||||
|
icon="heroicons:star-20-solid"
|
||||||
|
className="text-xl cursor-pointer text-[#FFCE30]"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Icon
|
||||||
|
icon="heroicons:star"
|
||||||
|
className="text-xl cursor-pointer text-default-400"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="flex-1 overflow-hidden text-sm text-default-600 truncate">{title}</p>
|
||||||
|
|
||||||
|
<div className="-space-x-1.5 rtl:space-x-reverse flex flex-nowrap">
|
||||||
|
{
|
||||||
|
image.map((item, index) =>
|
||||||
|
<TooltipProvider key={`avatar-${index}`} >
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Avatar className="h-7 w-7 border-none shadow-none">
|
||||||
|
<AvatarImage src={item.image} />
|
||||||
|
<AvatarFallback>SA</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{item.label}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-1 items-center">
|
||||||
|
{category.map((item, index) => (
|
||||||
|
<Badge
|
||||||
|
key={`category-${index}`}
|
||||||
|
className={cn("rounded-full px-3 py-1 font-medium", categoryClasses[item.value])}>
|
||||||
|
{item.label}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className="bg-transparent text-default-400 ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent w-fit"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setOpenEdit(true)}
|
||||||
|
>
|
||||||
|
<SquarePen className=" w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="bg-transparent text-default-400 ring-offset-transparent hover:text-destructive border-none focus-visible:ring-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent px-0 w-fit"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setConfirmDelete(true)}
|
||||||
|
>
|
||||||
|
<Trash2 className=" w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Todo;
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
'use client'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { useMediaQuery } from '@/hooks/use-media-query'
|
||||||
|
import { useTodoConfig } from '@/hooks/use-todo'
|
||||||
|
import { Menu } from 'lucide-react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const ToggleTodoSidebar = () => {
|
||||||
|
const isTablet = useMediaQuery("(min-width: 1024px)");
|
||||||
|
const [todoConfig, setTodoConfig] = useTodoConfig()
|
||||||
|
if (isTablet) return null
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button size='icon' color='secondary' onClick={() => setTodoConfig({ ...todoConfig, isOpen: !todoConfig.isOpen })}>
|
||||||
|
<Menu className=' h-5 w-5' />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ToggleTodoSidebar
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Basic Widget',
|
||||||
|
description: 'Basic Widget'
|
||||||
|
}
|
||||||
|
const BasicWidget = ({children}: {children: React.ReactNode}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BasicWidget;
|
||||||
|
|
@ -0,0 +1,532 @@
|
||||||
|
|
||||||
|
import { UpgradeBlock } from "@/components/blocks/upgrade-block";
|
||||||
|
import { WelcomeBlock, BlockBadge } from "@/components/blocks/welcome-block";
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Eye } from "lucide-react";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
const WidgetPage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="space-y-5" >
|
||||||
|
<div className="grid lg:grid-cols-3 md:grid-cols-2 grid-cols-1 gap-5">
|
||||||
|
{/* Block 1 */}
|
||||||
|
<WelcomeBlock >
|
||||||
|
<div className="max-w-[180px] relative z-10">
|
||||||
|
<h4 className="text-xl font-medium text-primary-foreground dark:text-default-900 mb-2">
|
||||||
|
Upgrade your Dashcode
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-primary-foreground dark:text-default-800 text-opacity-80">
|
||||||
|
Pro plan for better results
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<BlockBadge>Now</BlockBadge>
|
||||||
|
<Image
|
||||||
|
src="/images/all-img/widget-bg-3.png"
|
||||||
|
width={400}
|
||||||
|
height={150}
|
||||||
|
priority
|
||||||
|
alt="Description of the image"
|
||||||
|
className="absolute top-0 start-0 w-full h-full object-cover rounded-md"
|
||||||
|
/>
|
||||||
|
</WelcomeBlock>
|
||||||
|
{/* Block 2 */}
|
||||||
|
<WelcomeBlock >
|
||||||
|
<div className="max-w-[180px] relative z-10">
|
||||||
|
<div className="text-xl font-medium text-default-900 dark:text-default-100 mb-2">
|
||||||
|
Upgrade your Dashcode
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-default-800 dark:text-default-100">
|
||||||
|
Pro plan for better results
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<BlockBadge>Now</BlockBadge>
|
||||||
|
<Image
|
||||||
|
src="/images/all-img/widget-bg-1.png"
|
||||||
|
width={400}
|
||||||
|
height={150}
|
||||||
|
alt="Description of the image"
|
||||||
|
className="absolute top-0 start-0 w-full h-full object-cover rounded-md"
|
||||||
|
/>
|
||||||
|
</WelcomeBlock>
|
||||||
|
{/* Block 3 */}
|
||||||
|
<WelcomeBlock >
|
||||||
|
<div className="max-w-[180px] relative z-10">
|
||||||
|
<div className="text-xl font-medium text-default-900 dark:text-default-100 mb-2">
|
||||||
|
Upgrade your Dashcode
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-default-800 dark:text-default-100">
|
||||||
|
Pro plan for better results
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<BlockBadge>Now</BlockBadge>
|
||||||
|
<Image
|
||||||
|
src="/images/all-img/widget-bg-3-1.png"
|
||||||
|
width={400}
|
||||||
|
height={150}
|
||||||
|
alt="Description of the image"
|
||||||
|
className="absolute top-0 start-0 w-full h-full object-cover rounded-md"
|
||||||
|
/>
|
||||||
|
</WelcomeBlock>
|
||||||
|
{/* Block 4 */}
|
||||||
|
<WelcomeBlock >
|
||||||
|
<div className="max-w-[180px] relative z-10">
|
||||||
|
<h4 className="text-xl font-medium text-primary-foreground dark:text-default-900 mb-2">
|
||||||
|
<span className="block font-normal">Good evening,</span>
|
||||||
|
<span className="block">Mr. Dianne Russell</span>
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-primary-foreground dark:text-default-900 font-normal">
|
||||||
|
Welcome to Dashcode
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Image
|
||||||
|
src="/images/all-img/widget-bg-2.png"
|
||||||
|
width={400}
|
||||||
|
height={150}
|
||||||
|
alt="Description of the image"
|
||||||
|
className="absolute top-0 start-0 w-full h-full object-cover rounded-md"
|
||||||
|
/>
|
||||||
|
</WelcomeBlock>
|
||||||
|
{/* Block 5 */}
|
||||||
|
<WelcomeBlock className="flex items-center" >
|
||||||
|
<div className="flex-1 relative z-10">
|
||||||
|
<div className="max-w-[180px]">
|
||||||
|
<div className="text-xl font-medium text-default-900 dark:text-default-100 mb-2">
|
||||||
|
<span className="block font-normal">Good evening,</span>
|
||||||
|
<span className="block">Mr. Dianne Russell</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-default-900 dark:text-default-100 font-normal">
|
||||||
|
Welcome to Dashcode
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-none relative w-[120px] z-10">
|
||||||
|
<Image
|
||||||
|
src="/images/all-img/widgetvector.png"
|
||||||
|
alt=""
|
||||||
|
width={220}
|
||||||
|
height={100}
|
||||||
|
className="w-full h-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Image
|
||||||
|
src="/images/all-img/widget-bg-4.png"
|
||||||
|
width={400}
|
||||||
|
height={150}
|
||||||
|
alt="Description of the image"
|
||||||
|
className="absolute top-0 start-0 w-full h-full object-cover rounded-md"
|
||||||
|
/>
|
||||||
|
</WelcomeBlock>
|
||||||
|
{/* block 6 */}
|
||||||
|
<WelcomeBlock className="flex items-center" >
|
||||||
|
<div className="flex-1 relative z-10">
|
||||||
|
<div className="max-w-[180px]">
|
||||||
|
<div className="text-xl font-medium text-primary-foreground mb-2">
|
||||||
|
<span className="block font-normal">Good evening,</span>
|
||||||
|
<span className="block">Mr. Dianne Russell</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-wgite text-primary-foreground font-normal">
|
||||||
|
Welcome to Dashcode
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-none relative z-10">
|
||||||
|
<Image
|
||||||
|
alt=""
|
||||||
|
src="/images/all-img/widgetvector2.png"
|
||||||
|
className="ms-auto w-[100px] h-[80px]"
|
||||||
|
width={120}
|
||||||
|
height={100}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Image
|
||||||
|
src="/images/all-img/widget-bg-5.png"
|
||||||
|
width={400}
|
||||||
|
height={150}
|
||||||
|
priority
|
||||||
|
alt="Description of the image"
|
||||||
|
className="absolute top-0 start-0 w-full h-full object-cover rounded-md"
|
||||||
|
/>
|
||||||
|
</WelcomeBlock>
|
||||||
|
{/* Block 7 */}
|
||||||
|
<WelcomeBlock className="bg-default" >
|
||||||
|
<div className="max-w-[180px] relative z-10">
|
||||||
|
<h4 className="text-xl font-medium text-primary-foreground mb-2">
|
||||||
|
Upgrade your Dashcode
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-primary-foreground text-opacity-80">
|
||||||
|
Pro plan for better results
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<BlockBadge className="bg-destructive text-destructive-foreground dark:bg-default-100 dark:text-default-900">Now</BlockBadge>
|
||||||
|
|
||||||
|
</WelcomeBlock>
|
||||||
|
{/* Block 8 */}
|
||||||
|
<WelcomeBlock className="bg-primary" >
|
||||||
|
<div className="max-w-[180px] relative z-10">
|
||||||
|
<h4 className="text-xl font-medium text-primary-foreground dark:text-default-900 mb-2">
|
||||||
|
Upgrade your Dashcode
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-primary-foreground dark:text-default-900 text-opacity-80">
|
||||||
|
Pro plan for better results
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<BlockBadge className="bg-success text-success-foreground">Now</BlockBadge>
|
||||||
|
</WelcomeBlock>
|
||||||
|
{/* Block 9 */}
|
||||||
|
<WelcomeBlock className="bg-gradient-to-r from-primary to-success" >
|
||||||
|
<div className="max-w-[180px] relative z-10">
|
||||||
|
<h4 className="text-xl font-medium text-primary-foreground dark:text-default-900 mb-2">
|
||||||
|
Upgrade your Dashcode
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-primary-foreground dark:text-default-900 text-opacity-80">
|
||||||
|
Pro plan for better results
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<BlockBadge className="bg-warning text-warning-foreground">Now</BlockBadge>
|
||||||
|
</WelcomeBlock>
|
||||||
|
{/* Block 10 */}
|
||||||
|
<WelcomeBlock className="flex items-center py-7" >
|
||||||
|
<div className="flex-1 relative z-10">
|
||||||
|
<div className="max-w-[180px]">
|
||||||
|
<h4 className="text-2xl font-medium text-primary-foreground dark:text-default-900 mb-2">
|
||||||
|
<span className="block text-sm">Current balance,</span>
|
||||||
|
<span className="block">$34,564</span>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-none relative z-10">
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
className="hover:ring-0 hover:ring-offset-0 "
|
||||||
|
>
|
||||||
|
<Eye className="w-4 h-4 text-primary-foreground me-2" />
|
||||||
|
View details
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Image
|
||||||
|
src="/images/all-img/widget-bg-6.png"
|
||||||
|
width={400}
|
||||||
|
height={150}
|
||||||
|
alt="Description of the image"
|
||||||
|
className="absolute top-0 start-0 w-full h-full object-cover rounded-md"
|
||||||
|
/>
|
||||||
|
</WelcomeBlock>
|
||||||
|
{/* Block 11 */}
|
||||||
|
<WelcomeBlock className="flex items-center py-7" >
|
||||||
|
<div className="flex-1 relative z-10">
|
||||||
|
<div className="max-w-[180px]">
|
||||||
|
<h4 className="text-2xl font-medium mb-2">
|
||||||
|
<span className="block text-sm text-default-800 dark:text-default-100">Current balance,</span>
|
||||||
|
<span className="block text-default-900 dark:text-default-100">$34,564</span>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-none relative z-10">
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
className="hover:ring-0 hover:ring-offset-0 "
|
||||||
|
>
|
||||||
|
<Eye className="w-4 h-4 text-primary-foreground me-2" />
|
||||||
|
View details
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Image
|
||||||
|
src="/images/all-img/widget-bg-7.png"
|
||||||
|
width={400}
|
||||||
|
height={150}
|
||||||
|
priority
|
||||||
|
alt="Description of the image"
|
||||||
|
className="absolute top-0 start-0 w-full h-full object-cover rounded-md"
|
||||||
|
/>
|
||||||
|
</WelcomeBlock>
|
||||||
|
{/* Block 12 */}
|
||||||
|
<WelcomeBlock className="flex items-center py-7" >
|
||||||
|
<div className="flex-1 relative z-10">
|
||||||
|
<div className="max-w-[180px]">
|
||||||
|
<h4 className="text-2xl font-medium mb-2">
|
||||||
|
<span className="block text-sm text-default-800 dark:text-default-100">Current balance,</span>
|
||||||
|
<span className="block text-default-900 dark:text-default-100">$34,564</span>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-none relative z-10">
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
className="hover:ring-0 hover:ring-offset-0 "
|
||||||
|
>
|
||||||
|
<Eye className="w-4 h-4 text-primary-foreground me-2" />
|
||||||
|
View details
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Image
|
||||||
|
src="/images/all-img/widget-bg-8.png"
|
||||||
|
width={400}
|
||||||
|
height={150}
|
||||||
|
priority
|
||||||
|
alt="Description of the image"
|
||||||
|
className="absolute top-0 start-0 w-full h-full object-cover rounded-md"
|
||||||
|
/>
|
||||||
|
</WelcomeBlock>
|
||||||
|
</div>
|
||||||
|
{/* Upgrade Block */}
|
||||||
|
<div className="grid xl:grid-cols-4 md:grid-cols-2 grid-cols-1 gap-5">
|
||||||
|
<UpgradeBlock className="bg-default dark:bg-default-50">
|
||||||
|
<div className="max-w-[168px] relative z-10">
|
||||||
|
<div className="text-base font-medium text-default-foreground dark:text-default-900">Unlimited Access</div>
|
||||||
|
<div className="text-xs font-normal text-default-foreground dark:text-default-800">
|
||||||
|
Upgrade your system to business plan
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 mb-14 relative z-10">
|
||||||
|
<Button
|
||||||
|
size="md"
|
||||||
|
className="bg-default-foreground text-default hover:bg-default-foreground hover:opacity-80 dark:bg-default dark:text-default-100 font-medium"
|
||||||
|
>
|
||||||
|
Upgrade
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="absolute bottom-0 start-0 z-10 w-full">
|
||||||
|
<Image
|
||||||
|
src="/images/svg/line.svg"
|
||||||
|
width={500}
|
||||||
|
height={200}
|
||||||
|
alt="Line Image"
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="absolute -bottom-4 end-5">
|
||||||
|
<Image
|
||||||
|
src="/images/svg/rabit.svg"
|
||||||
|
width={96}
|
||||||
|
height={96}
|
||||||
|
alt="Rabbit"
|
||||||
|
draggable={false}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</UpgradeBlock>
|
||||||
|
<UpgradeBlock className="bg-primary">
|
||||||
|
<div className="max-w-[168px] relative z-10">
|
||||||
|
<div className="text-base font-medium text-default-foreground dark:text-default-900">Unlimited Access</div>
|
||||||
|
<div className="text-xs font-normal text-default-foreground dark:text-default-800">
|
||||||
|
Upgrade your system to business plan
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 mb-14 relative z-10">
|
||||||
|
<Button
|
||||||
|
size="md"
|
||||||
|
className="bg-default-foreground text-default hover:bg-default-foreground hover:opacity-80 dark:bg-default dark:text-default-100 font-medium"
|
||||||
|
>
|
||||||
|
Upgrade
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="absolute bottom-0 start-0 z-10 w-full">
|
||||||
|
<Image
|
||||||
|
src="/images/svg/line.svg"
|
||||||
|
width={500}
|
||||||
|
height={200}
|
||||||
|
alt="Line Image"
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="absolute -bottom-4 end-5">
|
||||||
|
<Image
|
||||||
|
src="/images/svg/rabit.svg"
|
||||||
|
width={96}
|
||||||
|
height={96}
|
||||||
|
alt="Rabbit"
|
||||||
|
draggable={false}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</UpgradeBlock>
|
||||||
|
<UpgradeBlock className="bg-success">
|
||||||
|
<div className="max-w-[168px] relative z-10">
|
||||||
|
<div className="text-base font-medium text-default-foreground dark:text-default-900">Unlimited Access</div>
|
||||||
|
<div className="text-xs font-normal text-default-foreground dark:text-default-800">
|
||||||
|
Upgrade your system to business plan
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 mb-14 relative z-10">
|
||||||
|
<Button
|
||||||
|
size="md"
|
||||||
|
className="bg-default-foreground text-default hover:bg-default-foreground hover:opacity-80 dark:bg-default dark:text-default-100 font-medium"
|
||||||
|
>
|
||||||
|
Upgrade
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="absolute bottom-0 start-0 z-10 w-full">
|
||||||
|
<Image
|
||||||
|
src="/images/svg/line.svg"
|
||||||
|
width={500}
|
||||||
|
height={200}
|
||||||
|
alt="Line Image"
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="absolute -bottom-4 end-5">
|
||||||
|
<Image
|
||||||
|
src="/images/svg/rabit.svg"
|
||||||
|
width={96}
|
||||||
|
height={96}
|
||||||
|
alt="Rabbit"
|
||||||
|
draggable={false}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</UpgradeBlock>
|
||||||
|
<UpgradeBlock className="bg-info">
|
||||||
|
<div className="max-w-[168px] relative z-10">
|
||||||
|
<div className="text-base font-medium text-default-foreground dark:text-default-900">Unlimited Access</div>
|
||||||
|
<div className="text-xs font-normal text-default-foreground dark:text-default-800">
|
||||||
|
Upgrade your system to business plan
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 mb-14 relative z-10">
|
||||||
|
<Button
|
||||||
|
size="md"
|
||||||
|
className="bg-default-foreground text-default hover:bg-default-foreground hover:opacity-80 dark:bg-default dark:text-default-100 font-medium"
|
||||||
|
>
|
||||||
|
Upgrade
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="absolute bottom-0 start-0 z-10 w-full">
|
||||||
|
<Image
|
||||||
|
src="/images/svg/line.svg"
|
||||||
|
width={500}
|
||||||
|
height={200}
|
||||||
|
alt="Line Image"
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="absolute -bottom-4 end-5">
|
||||||
|
<Image
|
||||||
|
src="/images/svg/rabit.svg"
|
||||||
|
width={96}
|
||||||
|
height={96}
|
||||||
|
alt="Rabbit"
|
||||||
|
draggable={false}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</UpgradeBlock>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{/* gift block */}
|
||||||
|
|
||||||
|
<div className="grid xl:grid-cols-6 lg:grid-cols-4 md:grid-cols-2 grid-cols-1 gap-5">
|
||||||
|
<UpgradeBlock
|
||||||
|
className="bg-default dark:bg-default-200 mt-14">
|
||||||
|
<div className="absolute start-1/2 top-0 -translate-x-1/2 -translate-y-1/2">
|
||||||
|
<Image
|
||||||
|
src="/images/svg/gift.svg"
|
||||||
|
alt="image"
|
||||||
|
height={72}
|
||||||
|
width={72}
|
||||||
|
className="h-16 w-16"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="max-w-[160px] mx-auto mt-10">
|
||||||
|
<div className="text-base font-medium text-default-foreground dark:text-default-900">Unlimited Access</div>
|
||||||
|
<div className="text-xs font-normal text-default-foreground dark:text-default-900 mt-1">
|
||||||
|
Upgrade your system to business plan
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6">
|
||||||
|
<Button fullWidth size="md"
|
||||||
|
className="bg-primary-foreground dark:bg-white hover:ring-0 hover:ring-offset-0 ring-offset-transparent dark:text-default-100 hover:bg-primary-foreground hover:text-default hover:opacity-90 text-default">
|
||||||
|
Upgrade
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</UpgradeBlock>
|
||||||
|
{
|
||||||
|
["primary", "success", "info", "destructive", "warning"].map((item, index) => (
|
||||||
|
<UpgradeBlock
|
||||||
|
key={index}
|
||||||
|
className={`bg-${item} mt-14`}>
|
||||||
|
<div className="absolute start-1/2 top-0 -translate-x-1/2 -translate-y-1/2">
|
||||||
|
<Image
|
||||||
|
src="/images/svg/gift.svg"
|
||||||
|
alt="image"
|
||||||
|
height={72}
|
||||||
|
width={72}
|
||||||
|
className="h-16 w-16"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="max-w-[160px] mx-auto mt-10">
|
||||||
|
<div className="text-base font-medium text-default-foreground dark:text-default-900">Unlimited Access</div>
|
||||||
|
<div className="text-xs font-normal text-default-foreground dark:text-default-900 mt-1">
|
||||||
|
Upgrade your system to business plan
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6">
|
||||||
|
<Button fullWidth size="md" className="bg-primary-foreground hover:ring-0 hover:ring-offset-0 ring-offset-transparent dark:bg-white dark:text-default-100 hover:bg-primary-foreground hover:text-default hover:opacity-90 text-default">
|
||||||
|
Upgrade
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</UpgradeBlock>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{/* rabbit block */}
|
||||||
|
<div className="grid grid-cols-6 gap-5">
|
||||||
|
<UpgradeBlock
|
||||||
|
className="bg-default dark:bg-default-200 mt-14">
|
||||||
|
<div className="absolute start-1/2 top-0 -translate-x-1/2 -translate-y-1/2">
|
||||||
|
<Image
|
||||||
|
src="/images/svg/rabit.svg"
|
||||||
|
alt="image"
|
||||||
|
height={100}
|
||||||
|
width={100}
|
||||||
|
className="h-24 w-24"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="max-w-[160px] mx-auto mt-14">
|
||||||
|
<div className="text-base font-medium text-default-foreground dark:text-default-900">Unlimited Access</div>
|
||||||
|
<div className="text-xs font-normal text-default-foreground mt-1 dark:text-default-900">
|
||||||
|
Upgrade your system to business plan
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6">
|
||||||
|
<Button fullWidth size="md" className="bg-primary-foreground hover:ring-0 hover:ring-offset-0 ring-offset-transparent dark:bg-white dark:text-default-100 hover:bg-primary-foreground hover:text-default hover:opacity-90 text-default">
|
||||||
|
Upgrade
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</UpgradeBlock>
|
||||||
|
{
|
||||||
|
["primary", "success", "info", "destructive", "warning"].map((item, index) => (
|
||||||
|
<UpgradeBlock key={index} className={`bg-${item} mt-14`}>
|
||||||
|
<div className="absolute start-1/2 top-0 -translate-x-1/2 -translate-y-1/2">
|
||||||
|
<Image
|
||||||
|
src="/images/svg/rabit.svg"
|
||||||
|
alt="image"
|
||||||
|
height={100}
|
||||||
|
width={100}
|
||||||
|
className="h-24 w-24"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="max-w-[160px] mx-auto mt-14">
|
||||||
|
<div className="text-base font-medium text-default-foreground dark:text-default-900">Unlimited Access</div>
|
||||||
|
<div className="text-xs font-normal text-default-foreground dark:text-default-900 mt-1">
|
||||||
|
Upgrade your system to business plan
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6">
|
||||||
|
<Button fullWidth size="md" className="bg-primary-foreground hover:ring-0 hover:ring-offset-0 ring-offset-transparent dark:bg-white dark:text-default-100 hover:bg-primary-foreground hover:text-default hover:opacity-90 text-default">
|
||||||
|
Upgrade
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</UpgradeBlock>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WidgetPage;
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Dashcode Next Js",
|
||||||
|
description: "Dashcode is a popular dashboard template.",
|
||||||
|
};
|
||||||
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Statistic Widget',
|
||||||
|
description: 'Statistic Widget Description'
|
||||||
|
}
|
||||||
|
const StatisticWidget = ({children}: {children: React.ReactNode}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatisticWidget;
|
||||||
|
|
@ -0,0 +1,340 @@
|
||||||
|
import Image from "next/image";
|
||||||
|
import OrdersBlock from "@/components/blocks/orders-block";
|
||||||
|
import ProgressBlock from "@/components/blocks/progress-block";
|
||||||
|
import { StatisticsBlock } from "@/components/blocks/statistics-block";
|
||||||
|
import { StatusBlock } from "@/components/blocks/status-block";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
import { Box, TrendingDown, TrendingUp, ShoppingCart } from "lucide-react";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { BarChart } from "lucide-react";
|
||||||
|
import EarningBlock from "@/components/blocks/earning-block";
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
|
||||||
|
const StatisticPage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||||
|
<StatusBlock
|
||||||
|
title="Total Revenue"
|
||||||
|
total="3,564"
|
||||||
|
iconWrapperClass="bg-info/10"
|
||||||
|
chartColor="#00EBFF"
|
||||||
|
icon={<ShoppingCart className="w-5 h-5 text-info" />}
|
||||||
|
/>
|
||||||
|
<StatusBlock
|
||||||
|
title="Products sold"
|
||||||
|
total="564"
|
||||||
|
icon={<Box className="w-5 h-5 text-warning" />}
|
||||||
|
iconWrapperClass="bg-warning/10"
|
||||||
|
chartColor="#FB8F65"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StatusBlock
|
||||||
|
title="Growth"
|
||||||
|
total="+5.0%"
|
||||||
|
icon={<TrendingUp className="w-5 h-5 text-primary" />}
|
||||||
|
iconWrapperClass="bg-primary/10"
|
||||||
|
chartColor="#2563eb"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||||
|
<StatisticsBlock
|
||||||
|
title="Total Revenue"
|
||||||
|
total="3,564"
|
||||||
|
className="bg-info/10"
|
||||||
|
/>
|
||||||
|
<StatisticsBlock
|
||||||
|
title="Products sold"
|
||||||
|
total="564"
|
||||||
|
className="bg-warning/10"
|
||||||
|
chartColor="#FB8F65"
|
||||||
|
/>
|
||||||
|
<StatisticsBlock
|
||||||
|
title="Growth"
|
||||||
|
total="+5.0%"
|
||||||
|
className="bg-primary/10"
|
||||||
|
chartColor="#2563eb"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Card>
|
||||||
|
<CardContent className=" p-6">
|
||||||
|
<div className="grid xl:grid-cols-4 lg:grid-cols-2 md:grid-cols-2 grid-cols-1 gap-5 place-content-center">
|
||||||
|
<div className="flex space-x-4 h-full items-center rtl:space-x-reverse">
|
||||||
|
<div className="flex-none">
|
||||||
|
<Avatar className="h-20 w-20 bg-transparent hover:bg-transparent">
|
||||||
|
<AvatarImage src="/images/all-img/main-user.png" />
|
||||||
|
<AvatarFallback>SA</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h4 className="text-xl font-medium mb-2">
|
||||||
|
<span className="block font-light text-default-800">Good evening,</span>
|
||||||
|
<span className="block text-default-900">Mr. Jone Doe</span>
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-default-600">Welcome to Dashcode</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* status blocks */}
|
||||||
|
<StatusBlock
|
||||||
|
title="Current balance"
|
||||||
|
total="$34,564"
|
||||||
|
chartType="bar"
|
||||||
|
className="bg-secondary/10 shadow-none rounded"
|
||||||
|
opacity={1}
|
||||||
|
/>
|
||||||
|
<StatusBlock
|
||||||
|
title="Credit"
|
||||||
|
total="$3,564"
|
||||||
|
chartColor="#80fac1"
|
||||||
|
className="bg-secondary/10 shadow-none rounded"
|
||||||
|
series={[40, 70, 45, 100, 75, 40, 80, 90]}
|
||||||
|
chartType="bar"
|
||||||
|
opacity={1}
|
||||||
|
/>
|
||||||
|
<StatusBlock
|
||||||
|
title="Debit"
|
||||||
|
total="$3,564"
|
||||||
|
chartColor="#ffbf99"
|
||||||
|
className="bg-secondary/10 shadow-none rounded"
|
||||||
|
chartType="bar"
|
||||||
|
series={[40, 70, 45, 100, 75, 40, 80, 90]}
|
||||||
|
opacity={1}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 ">
|
||||||
|
<Card className="bg-warning/20 relative">
|
||||||
|
<CardContent className="p-4 ">
|
||||||
|
<Image
|
||||||
|
src="/images/all-img/shade-1.png"
|
||||||
|
alt="images"
|
||||||
|
draggable="false"
|
||||||
|
className="absolute top-0 start-0 w-full h-full object-contain"
|
||||||
|
width={300}
|
||||||
|
height={200}
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
<div className="mb-6 text-sm text-default-900 font-medium">Sales </div>
|
||||||
|
<div className=" text-2xl text-default-900 font-medium mb-6"> 354</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex-none text-xl">
|
||||||
|
<TrendingUp className="w-4 h-4 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 text-sm">
|
||||||
|
<span className="block mb-0.2">25.67% </span>
|
||||||
|
<span className="block mb-1 text-default-600">
|
||||||
|
From last week
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card className="bg-info/20 relative">
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<Image
|
||||||
|
src="/images/all-img/shade-2.png"
|
||||||
|
alt="images"
|
||||||
|
draggable="false"
|
||||||
|
className="absolute top-0 start-0 w-full h-full object-contain"
|
||||||
|
width={300}
|
||||||
|
height={200}
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
<div className="mb-6 text-sm text-default-900 font-medium">Revenue </div>
|
||||||
|
<div className=" text-2xl text-default-900 font-medium mb-6">$86,954</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex-none text-xl">
|
||||||
|
<TrendingUp className="w-4 h-4 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 text-sm">
|
||||||
|
<span className="block mb-0.2">8.67% </span>
|
||||||
|
<span className="block mb-1 text-default-600">
|
||||||
|
From last week
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card className="bg-primary/20 relative">
|
||||||
|
<CardContent className=" p-4">
|
||||||
|
<Image
|
||||||
|
src="/images/all-img/shade-3.png"
|
||||||
|
alt="images"
|
||||||
|
draggable="false"
|
||||||
|
className="absolute top-0 start-0 w-full h-full object-contain"
|
||||||
|
width={300}
|
||||||
|
height={200}
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
<div className="mb-6 text-sm text-default-900 font-medium">Conversion </div>
|
||||||
|
<div className=" text-2xl text-default-900 font-medium mb-6">15%</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex-none text-xl">
|
||||||
|
<TrendingDown className="w-4 h-4 text-destructive" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 text-sm">
|
||||||
|
<span className="block mb-0.2 text-destructive">1.67% </span>
|
||||||
|
<span className="block mb-1 text-default-600">
|
||||||
|
From last week
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card className="bg-success/20 relative">
|
||||||
|
<CardContent className=" p-4">
|
||||||
|
<Image
|
||||||
|
src="/images/all-img/shade-4.png"
|
||||||
|
alt="images"
|
||||||
|
draggable="false"
|
||||||
|
className="absolute top-0 start-0 w-full h-full object-contain"
|
||||||
|
width={300}
|
||||||
|
height={200}
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
<div className="mb-6 text-sm text-default-900 font-medium">Leads </div>
|
||||||
|
<div className=" text-2xl text-default-900 font-medium mb-6">654</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex-none text-xl">
|
||||||
|
<TrendingDown className="w-4 h-4 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 text-sm">
|
||||||
|
<span className="block mb-0.2 text-primary">1.67% </span>
|
||||||
|
<span className="block mb-1 text-default-600">
|
||||||
|
From last week
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 ">
|
||||||
|
<Card className="bg-info/20">
|
||||||
|
<CardContent className=" p-4 text-center">
|
||||||
|
<div className="mx-auto h-10 w-10 rounded-full flex items-center justify-center bg-white mb-4">
|
||||||
|
<BarChart className=" h-6 w-6 text-info" />
|
||||||
|
</div>
|
||||||
|
<div className="block text-sm text-default-600 font-medium mb-1.5"> Total Task</div>
|
||||||
|
<div className="text-2xl text-default-900 font-medium"> 64</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card className="bg-warning/20">
|
||||||
|
<CardContent className=" p-4 text-center">
|
||||||
|
<div
|
||||||
|
className="mx-auto h-10 w-10 rounded-full flex items-center justify-center bg-white mb-4"
|
||||||
|
>
|
||||||
|
<Icon className="w-6 h-6 text-warning" icon="heroicons:chart-pie" />
|
||||||
|
</div>
|
||||||
|
<div className="block text-sm text-default-600 font-medium mb-1.5">Completed</div>
|
||||||
|
<div className="text-2xl text-default-900 font-medium">45</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card className="bg-primary/20">
|
||||||
|
<CardContent className=" p-4 text-center">
|
||||||
|
<div
|
||||||
|
className="mx-auto h-10 w-10 rounded-full flex items-center justify-center bg-white mb-4"
|
||||||
|
>
|
||||||
|
<Icon className="w-6 h-6 text-primary" icon="heroicons:clock" />
|
||||||
|
</div>
|
||||||
|
<div className="block text-sm text-default-600 font-medium mb-1.5">Hours</div>
|
||||||
|
<div className="text-2xl text-default-900 font-medium">190</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card className="bg-success/20">
|
||||||
|
<CardContent className="p-4 text-center">
|
||||||
|
<div
|
||||||
|
className="mx-auto h-10 w-10 rounded-full flex items-center justify-center bg-white mb-4"
|
||||||
|
>
|
||||||
|
<Icon className="w-6 h-6 text-success" icon="heroicons:calculator" />
|
||||||
|
</div>
|
||||||
|
<div className="block text-sm text-default-600 font-medium mb-1.5">Spendings</div>
|
||||||
|
<div className="text-2xl text-default-900 font-medium">$3,564</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
{/* progress */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 ">
|
||||||
|
<ProgressBlock
|
||||||
|
title="Progress"
|
||||||
|
/>
|
||||||
|
<ProgressBlock
|
||||||
|
title="Progress"
|
||||||
|
colors={["#F1595C", "#F9E1E5"]}
|
||||||
|
labels={["Completed", "In Progress"]}
|
||||||
|
/>
|
||||||
|
<ProgressBlock
|
||||||
|
title="Progress"
|
||||||
|
colors={["#50C793", "#E5F3E5"]}
|
||||||
|
labels={["Success", "Failure"]}
|
||||||
|
/>
|
||||||
|
<ProgressBlock
|
||||||
|
title="Progress"
|
||||||
|
colors={["#FA916B", "#fcc8b5"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-6 gap-4">
|
||||||
|
<OrdersBlock
|
||||||
|
title="Orders"
|
||||||
|
total="123k"
|
||||||
|
/>
|
||||||
|
<OrdersBlock
|
||||||
|
title="Orders"
|
||||||
|
total="123k"
|
||||||
|
chartColor="#f1595c"
|
||||||
|
/>
|
||||||
|
<OrdersBlock
|
||||||
|
title="Orders"
|
||||||
|
total="123k"
|
||||||
|
chartColor="#50c893"
|
||||||
|
/>
|
||||||
|
<OrdersBlock
|
||||||
|
title="Profit"
|
||||||
|
total="123k"
|
||||||
|
chartColor="#4669fa"
|
||||||
|
chartType="line"
|
||||||
|
percentageContent={<span className="text-primary">+2.5%</span>}
|
||||||
|
/>
|
||||||
|
<OrdersBlock
|
||||||
|
title="Profit"
|
||||||
|
total="123k"
|
||||||
|
chartColor="#f1595c"
|
||||||
|
chartType="line"
|
||||||
|
percentageContent={<span className="text-destructive">+2.5%</span>}
|
||||||
|
/>
|
||||||
|
<OrdersBlock
|
||||||
|
title="Profit"
|
||||||
|
total="123k"
|
||||||
|
chartColor="#50c893"
|
||||||
|
chartType="line"
|
||||||
|
percentageContent={<span className="text-success">+2.5%</span>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
<EarningBlock
|
||||||
|
title="Earnings"
|
||||||
|
total="$12,335.00"
|
||||||
|
percentage="+08%"
|
||||||
|
/>
|
||||||
|
<EarningBlock
|
||||||
|
title="Earnings"
|
||||||
|
total="$12,335.00"
|
||||||
|
percentage="+08%"
|
||||||
|
/>
|
||||||
|
<EarningBlock
|
||||||
|
title="Earnings"
|
||||||
|
total="$12,335.00"
|
||||||
|
percentage="+08%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatisticPage;
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Dashcode Next Js",
|
||||||
|
description: "Dashcode is a popular dashboard template.",
|
||||||
|
};
|
||||||
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
interface item {
|
||||||
|
version: string;
|
||||||
|
date: string;
|
||||||
|
changes: { name: string; tag?: string }[];
|
||||||
|
}
|
||||||
|
const ChangeLogPage = () => {
|
||||||
|
const t = useTranslations("Changelog")
|
||||||
|
const items: item[] = [
|
||||||
|
{
|
||||||
|
version: "Version 2.0.1",
|
||||||
|
date: "1 February 2023",
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
name: "Monochrome mode",
|
||||||
|
tag: "added",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Axios configuration",
|
||||||
|
tag: "fixed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Other minor issues",
|
||||||
|
tag: "fixed",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "Version 2.0.0",
|
||||||
|
date: "24 January 2023",
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
name: "Change log page added.",
|
||||||
|
tag: "added",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Badge added in sidebar.",
|
||||||
|
tag: "added",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Vuex replaced with pinia",
|
||||||
|
tag: "update",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Webpack replaced with Vite.",
|
||||||
|
tag: "update",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Other minor issues",
|
||||||
|
tag: "fixed",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "Version 1.0.1 ",
|
||||||
|
date: "3 January 2023",
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
name: "RTL version added.",
|
||||||
|
tag: "added",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sidebar updated.",
|
||||||
|
tag: "update",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Other minor issues",
|
||||||
|
tag: "fixed",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "Version 1.0.0 ",
|
||||||
|
date: "29 December 2022",
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
name: "Initial Release",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-12 gap-5">
|
||||||
|
<div className="lg:col-span-8 col-span-12">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>{t("version")}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Badge color="primary" className="dark:text-white">New</Badge>
|
||||||
|
<div className="mt-6">
|
||||||
|
<Accordion type="single" collapsible className="w-full">
|
||||||
|
{
|
||||||
|
items.map((item, index) => (
|
||||||
|
<AccordionItem
|
||||||
|
value={`value-${index + 1}`}
|
||||||
|
key={`changelog-${index}`}
|
||||||
|
className="border-default-100"
|
||||||
|
>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div>
|
||||||
|
{item.version}
|
||||||
|
<span className="font-semibold text-xs text-default-400">
|
||||||
|
- Published on {item.date}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{item.changes.map((data, j) => (
|
||||||
|
<div key={j}>
|
||||||
|
<div className="flex gap-3 items-center mt-2 text-default-600 text-sm">
|
||||||
|
<span className="h-2 w-2 bg-primary rounded-full"></span>
|
||||||
|
<span>{data.name}</span>
|
||||||
|
<span
|
||||||
|
className={` px-2 rounded-full text-xs capitalize
|
||||||
|
${data.tag === "added"
|
||||||
|
? "bg-primary/10 text-primary"
|
||||||
|
: data.tag === "update"
|
||||||
|
? "bg-yellow-100 text-yellow-500"
|
||||||
|
: data.tag === "fixed"
|
||||||
|
? "bg-destructive/10 text-destructive"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{data.tag}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<div className="lg:col-span-4 col-span-12">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>
|
||||||
|
{t("changelog")}
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<h5 className="text-xs font-medium">
|
||||||
|
{t("versionHistory")}
|
||||||
|
</h5>
|
||||||
|
<ul className="space-y-3 mt-6 divide-y divide-default-100 dark:divide-default-300">
|
||||||
|
{items.map((item, i) => (
|
||||||
|
<li
|
||||||
|
className="flex justify-between items-center text-xs text-default-600 pt-3"
|
||||||
|
key={i}
|
||||||
|
>
|
||||||
|
<span>{item.version} </span>
|
||||||
|
<span>{item.date}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChangeLogPage;
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
"use client";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||||
|
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { hexToRGB } from "@/lib/utils";
|
||||||
|
import { useConfig } from "@/hooks/use-config";
|
||||||
|
import {
|
||||||
|
getGridConfig,
|
||||||
|
getXAxisConfig,
|
||||||
|
getYAxisConfig,
|
||||||
|
} from "@/lib/appex-chart-options";
|
||||||
|
import { colors } from "@/lib/colors";
|
||||||
|
|
||||||
|
|
||||||
|
const BasicArea = ({ height = 300 }) => {
|
||||||
|
const [config] = useConfig();
|
||||||
|
|
||||||
|
const { theme: mode } = useTheme();
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
data: [90, 70, 85, 60, 80, 70, 90, 75, 60, 80],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const options: any = {
|
||||||
|
chart: {
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: "smooth",
|
||||||
|
width: 4,
|
||||||
|
},
|
||||||
|
colors: [colors.primary],
|
||||||
|
tooltip: {
|
||||||
|
theme: mode === "dark" ? "dark" : "light",
|
||||||
|
},
|
||||||
|
grid: getGridConfig(),
|
||||||
|
|
||||||
|
fill: {
|
||||||
|
type: "gradient",
|
||||||
|
colors: [colors.primary],
|
||||||
|
gradient: {
|
||||||
|
shadeIntensity: 1,
|
||||||
|
opacityFrom: 0.2,
|
||||||
|
opacityTo: 0.1,
|
||||||
|
stops: [50, 100, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: getYAxisConfig(mode === 'light' ? colors["default-600"] : colors["default-300"]),
|
||||||
|
xaxis: getXAxisConfig(
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
),
|
||||||
|
padding: {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
options={options}
|
||||||
|
series={series}
|
||||||
|
type="area"
|
||||||
|
height={height}
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BasicArea;
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,108 @@
|
||||||
|
"use client";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||||
|
import { colors } from "@/lib/colors";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { hexToRGB } from "@/lib/utils";
|
||||||
|
import { useConfig } from "@/hooks/use-config";
|
||||||
|
import {
|
||||||
|
getGridConfig,
|
||||||
|
getXAxisConfig,
|
||||||
|
getYAxisConfig,
|
||||||
|
} from "@/lib/appex-chart-options";
|
||||||
|
|
||||||
|
const IrregularTimeSeries = ({ height = 300 }) => {
|
||||||
|
const [config] = useConfig();
|
||||||
|
const { theme: mode } = useTheme();
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
name: "product A",
|
||||||
|
data: [100, 200, 300, 400, 500, 200, 100],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "product B",
|
||||||
|
data: [600, 700, 300, 500],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "product C",
|
||||||
|
data: [500, 100, 400, 700, 400, 700],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const options: any = {
|
||||||
|
chart: {
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: "smooth",
|
||||||
|
width: 4,
|
||||||
|
},
|
||||||
|
colors: [
|
||||||
|
colors.primary,
|
||||||
|
colors.success,
|
||||||
|
colors.info,
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
theme: mode === "dark" ? "dark" : "light",
|
||||||
|
},
|
||||||
|
grid: getGridConfig(),
|
||||||
|
|
||||||
|
fill: {
|
||||||
|
type: "gradient",
|
||||||
|
colors: [
|
||||||
|
colors.primary,
|
||||||
|
colors.success,
|
||||||
|
colors.info,
|
||||||
|
],
|
||||||
|
gradient: {
|
||||||
|
shadeIntensity: 1,
|
||||||
|
opacityFrom: 0.2,
|
||||||
|
opacityTo: 0.1,
|
||||||
|
stops: [20, 100, 100, 100],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: getYAxisConfig(
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
),
|
||||||
|
xaxis: getXAxisConfig(
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
),
|
||||||
|
padding: {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
colors: mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 5,
|
||||||
|
vertical: 5,
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
radius: 10,
|
||||||
|
offsetX: config.isRtl ? 5 : -5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
options={options}
|
||||||
|
series={series}
|
||||||
|
type="area"
|
||||||
|
height={height}
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IrregularTimeSeries;
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "Appex Area Chart",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
|
|
@ -0,0 +1,265 @@
|
||||||
|
"use client";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||||
|
import { colors } from "@/lib/colors";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { hexToRGB } from "@/lib/utils";
|
||||||
|
import { useConfig } from "@/hooks/use-config";
|
||||||
|
import {
|
||||||
|
getGridConfig,
|
||||||
|
getXAxisConfig,
|
||||||
|
getYAxisConfig,
|
||||||
|
} from "@/lib/appex-chart-options";
|
||||||
|
|
||||||
|
const NegativeAreaChart = ({ height = 320 }) => {
|
||||||
|
const [config] = useConfig();
|
||||||
|
const { theme: mode } = useTheme();
|
||||||
|
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
name: "north",
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
x: 1996,
|
||||||
|
y: 322,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 1997,
|
||||||
|
y: 324,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 1998,
|
||||||
|
y: 329,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 1999,
|
||||||
|
y: 342,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2000,
|
||||||
|
y: 348,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2001,
|
||||||
|
y: 334,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2002,
|
||||||
|
y: 325,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2003,
|
||||||
|
y: 316,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2004,
|
||||||
|
y: 318,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2005,
|
||||||
|
y: 330,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2006,
|
||||||
|
y: 355,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2007,
|
||||||
|
y: 366,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2008,
|
||||||
|
y: 337,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2009,
|
||||||
|
y: 352,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2010,
|
||||||
|
y: 377,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2011,
|
||||||
|
y: 383,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2012,
|
||||||
|
y: 344,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2013,
|
||||||
|
y: 366,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2014,
|
||||||
|
y: 389,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2015,
|
||||||
|
y: 334,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "south",
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
x: 1996,
|
||||||
|
y: 162,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 1997,
|
||||||
|
y: 90,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 1998,
|
||||||
|
y: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 1999,
|
||||||
|
y: 77,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2000,
|
||||||
|
y: 35,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2001,
|
||||||
|
y: -45,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2002,
|
||||||
|
y: -88,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2003,
|
||||||
|
y: -120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2004,
|
||||||
|
y: -156,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2005,
|
||||||
|
y: -123,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2006,
|
||||||
|
y: -88,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2007,
|
||||||
|
y: -66,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2008,
|
||||||
|
y: -45,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2009,
|
||||||
|
y: -29,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2010,
|
||||||
|
y: -45,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2011,
|
||||||
|
y: -88,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2012,
|
||||||
|
y: -132,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2013,
|
||||||
|
y: -146,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2014,
|
||||||
|
y: -169,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2015,
|
||||||
|
y: -184,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const options: any = {
|
||||||
|
chart: {
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: "smooth",
|
||||||
|
width: 4,
|
||||||
|
},
|
||||||
|
colors: [
|
||||||
|
colors.primary,
|
||||||
|
colors.info,
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
theme: mode === "dark" ? "dark" : "light",
|
||||||
|
},
|
||||||
|
grid: getGridConfig(),
|
||||||
|
|
||||||
|
fill: {
|
||||||
|
type: "gradient",
|
||||||
|
colors: [
|
||||||
|
colors.primary,
|
||||||
|
colors.info,
|
||||||
|
],
|
||||||
|
gradient: {
|
||||||
|
shadeIntensity: 1,
|
||||||
|
opacityFrom: 0.2,
|
||||||
|
opacityTo: 0.1,
|
||||||
|
stops: [50, 100, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: getYAxisConfig(
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
),
|
||||||
|
xaxis: getXAxisConfig(
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
),
|
||||||
|
padding: {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
colors: mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 5,
|
||||||
|
vertical: 5,
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
radius: 10,
|
||||||
|
offsetX: config.isRtl ? 5 : -5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
options={options}
|
||||||
|
series={series}
|
||||||
|
type="area"
|
||||||
|
height={height}
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NegativeAreaChart;
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
"use client";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||||
|
import { colors } from "@/lib/colors";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { hexToRGB } from "@/lib/utils";
|
||||||
|
import { useConfig } from "@/hooks/use-config";
|
||||||
|
import {
|
||||||
|
getGridConfig,
|
||||||
|
getYAxisConfig,
|
||||||
|
getLabel,
|
||||||
|
} from "@/lib/appex-chart-options";
|
||||||
|
|
||||||
|
const NullValueAreaChart = ({ height = 300 }) => {
|
||||||
|
const [config] = useConfig();
|
||||||
|
const { theme: mode } = useTheme();
|
||||||
|
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
name: "Network",
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
x: "Dec 23 2017",
|
||||||
|
y: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Dec 24 2017",
|
||||||
|
y: 44,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Dec 25 2017",
|
||||||
|
y: 31,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Dec 26 2017",
|
||||||
|
y: 38,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Dec 27 2017",
|
||||||
|
y: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Dec 28 2017",
|
||||||
|
y: 32,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Dec 29 2017",
|
||||||
|
y: 55,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Dec 30 2017",
|
||||||
|
y: 51,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Dec 31 2017",
|
||||||
|
y: 67,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Jan 01 2018",
|
||||||
|
y: 22,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Jan 02 2018",
|
||||||
|
y: 34,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Jan 03 2018",
|
||||||
|
y: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Jan 04 2018",
|
||||||
|
y: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Jan 05 2018",
|
||||||
|
y: 11,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Jan 06 2018",
|
||||||
|
y: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Jan 07 2018",
|
||||||
|
y: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Jan 08 2018",
|
||||||
|
y: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Jan 09 2018",
|
||||||
|
y: 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Jan 10 2018",
|
||||||
|
y: 34,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Jan 11 2018",
|
||||||
|
y: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Jan 12 2018",
|
||||||
|
y: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Jan 13 2018",
|
||||||
|
y: 13,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "Jan 14 2018",
|
||||||
|
y: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const options: any = {
|
||||||
|
chart: {
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: "straight",
|
||||||
|
},
|
||||||
|
colors: [
|
||||||
|
colors.primary,
|
||||||
|
colors.success,
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
theme: mode === "dark" ? "dark" : "light",
|
||||||
|
},
|
||||||
|
grid: getGridConfig(),
|
||||||
|
|
||||||
|
fill: {
|
||||||
|
type: "pattern",
|
||||||
|
colors: [
|
||||||
|
colors.primary,
|
||||||
|
colors.success,
|
||||||
|
],
|
||||||
|
pattern: {
|
||||||
|
style: ["verticalLines", "horizontalLines"],
|
||||||
|
width: 5,
|
||||||
|
height: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: getYAxisConfig(
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
),
|
||||||
|
xaxis: {
|
||||||
|
type: "datetime",
|
||||||
|
labels: getLabel(mode === 'light' ? colors["default-600"] : colors["default-300"]),
|
||||||
|
axisBorder: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTicks: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
options={options}
|
||||||
|
series={series}
|
||||||
|
type="area"
|
||||||
|
height={height}
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NullValueAreaChart;
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import BasicArea from "./basic-area";
|
||||||
|
import SplineArea from "./spline-area";
|
||||||
|
import IrregularTimeSeries from "./irregular-time-series";
|
||||||
|
import StackedAreaChart from "./stacked-area";
|
||||||
|
import NullValueAreaChart from "./null-value-area";
|
||||||
|
import GithubStyleChart from "./github-style-chart";
|
||||||
|
import { Link } from '@/i18n/routing';
|
||||||
|
import GithubStyleCharts1 from "./github-styles-charts1";
|
||||||
|
import NegativeAreaChart from "./negative-areachart";
|
||||||
|
import Image from "next/image";
|
||||||
|
import avatar7 from "@/public/images/avatar/avatar-7.png";
|
||||||
|
|
||||||
|
|
||||||
|
const AreaChartPage = () => {
|
||||||
|
return (
|
||||||
|
<div className=" grid xl:grid-cols-2 grid-cols-1 gap-6">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Basic Area Chart</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<BasicArea />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Spline Area Chart</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<SplineArea />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Area with Negative Values Chart</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<NegativeAreaChart />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Area Chart - Github Style</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<GithubStyleChart />
|
||||||
|
<div className="flex items-center space-x-2 rtl:space-x-reverse mt-2">
|
||||||
|
<div className="w-12 h-12">
|
||||||
|
<Image
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
src={avatar7}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="">
|
||||||
|
<Link
|
||||||
|
href="#"
|
||||||
|
className="text-base capitalize font-medium text-card-foreground"
|
||||||
|
>
|
||||||
|
coder
|
||||||
|
</Link>
|
||||||
|
<div className="cmeta space-x-1 rtl:space-x-reverse">
|
||||||
|
<span className="text-base font-bold text-card-foreground commits">
|
||||||
|
110
|
||||||
|
</span>
|
||||||
|
<span className="text-base text-card-foreground font-medium ">
|
||||||
|
commits
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<GithubStyleCharts1 />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Irregular Timeseries Chart</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<IrregularTimeSeries />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Stacked Area Chart</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<StackedAreaChart />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Area Chart With Null Values </CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<NullValueAreaChart />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AreaChartPage;
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
"use client";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||||
|
import { colors } from "@/lib/colors";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { hexToRGB } from "@/lib/utils";
|
||||||
|
import { useConfig } from "@/hooks/use-config";
|
||||||
|
import {
|
||||||
|
getGridConfig,
|
||||||
|
getXAxisConfig,
|
||||||
|
getYAxisConfig,
|
||||||
|
} from "@/lib/appex-chart-options";
|
||||||
|
|
||||||
|
const SplineArea = ({ height = 300 }) => {
|
||||||
|
const [config] = useConfig();
|
||||||
|
const { theme: mode } = useTheme();
|
||||||
|
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
name: "series1",
|
||||||
|
data: [31, 40, 28, 51, 42, 109, 100],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "series2",
|
||||||
|
data: [11, 32, 45, 32, 34, 52, 41],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const options: any = {
|
||||||
|
chart: {
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: "smooth",
|
||||||
|
width: 4,
|
||||||
|
},
|
||||||
|
colors: [
|
||||||
|
colors.primary,
|
||||||
|
colors.success,
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
theme: mode === "dark" ? "dark" : "light",
|
||||||
|
},
|
||||||
|
grid: getGridConfig(),
|
||||||
|
|
||||||
|
fill: {
|
||||||
|
type: "gradient",
|
||||||
|
colors: [
|
||||||
|
colors.primary,
|
||||||
|
colors.success,
|
||||||
|
],
|
||||||
|
gradient: {
|
||||||
|
shadeIntensity: 1,
|
||||||
|
opacityFrom: 0.2,
|
||||||
|
opacityTo: 0.1,
|
||||||
|
stops: [50, 100, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: getYAxisConfig(
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
),
|
||||||
|
xaxis: getXAxisConfig(
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
),
|
||||||
|
padding: {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
colors: mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 5,
|
||||||
|
vertical: 5,
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
radius: 10,
|
||||||
|
offsetX: config.isRtl ? 5 : -5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
options={options}
|
||||||
|
series={series}
|
||||||
|
type="area"
|
||||||
|
height={height}
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SplineArea;
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
"use client";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||||
|
import { colors } from "@/lib/colors";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { hexToRGB } from "@/lib/utils";
|
||||||
|
import { useConfig } from "@/hooks/use-config";
|
||||||
|
import {
|
||||||
|
getGridConfig,
|
||||||
|
getYAxisConfig,
|
||||||
|
getLabel,
|
||||||
|
} from "@/lib/appex-chart-options";
|
||||||
|
|
||||||
|
interface DataRange {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
}
|
||||||
|
const StackedAreaChart = ({ height = 300 }) => {
|
||||||
|
const [config] = useConfig();
|
||||||
|
const { theme: mode } = useTheme();
|
||||||
|
|
||||||
|
|
||||||
|
function generateDayWiseTimeSeries(baseval: number, count: number, yrange: DataRange) {
|
||||||
|
var i = 0;
|
||||||
|
var series: [number, number][] = [];
|
||||||
|
while (i < count) {
|
||||||
|
var x = baseval;
|
||||||
|
var y =
|
||||||
|
Math.floor(Math.random() * (yrange.max - yrange.min + 1)) + yrange.min;
|
||||||
|
|
||||||
|
series.push([x, y]);
|
||||||
|
baseval += 86400000;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return series;
|
||||||
|
}
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
name: "South",
|
||||||
|
data: generateDayWiseTimeSeries(
|
||||||
|
new Date("11 Feb 2017 GMT").getTime(),
|
||||||
|
20,
|
||||||
|
{
|
||||||
|
min: 10,
|
||||||
|
max: 60,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "North",
|
||||||
|
data: generateDayWiseTimeSeries(
|
||||||
|
new Date("11 Feb 2017 GMT").getTime(),
|
||||||
|
20,
|
||||||
|
{
|
||||||
|
min: 10,
|
||||||
|
max: 40,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Central",
|
||||||
|
data: generateDayWiseTimeSeries(
|
||||||
|
new Date("11 Feb 2017 GMT").getTime(),
|
||||||
|
20,
|
||||||
|
{
|
||||||
|
min: 10,
|
||||||
|
max: 30,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const options: any = {
|
||||||
|
chart: {
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: "smooth",
|
||||||
|
width: 4,
|
||||||
|
},
|
||||||
|
colors: [
|
||||||
|
colors.primary,
|
||||||
|
colors.primary,
|
||||||
|
colors.info,
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
theme: mode === "dark" ? "dark" : "light",
|
||||||
|
},
|
||||||
|
grid: getGridConfig(),
|
||||||
|
|
||||||
|
fill: {
|
||||||
|
type: "gradient",
|
||||||
|
colors: [
|
||||||
|
colors.primary,
|
||||||
|
colors.primary,
|
||||||
|
colors.info,
|
||||||
|
],
|
||||||
|
gradient: {
|
||||||
|
shadeIntensity: 1,
|
||||||
|
opacityFrom: 0.4,
|
||||||
|
opacityTo: 0.1,
|
||||||
|
stops: [50, 100, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: getYAxisConfig(
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
),
|
||||||
|
|
||||||
|
xaxis: {
|
||||||
|
type: "datetime",
|
||||||
|
axisBorder: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTicks: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
labels: getLabel(mode === 'light' ? colors["default-600"] : colors["default-300"]),
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
colors: mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 5,
|
||||||
|
vertical: 5,
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
radius: 10,
|
||||||
|
offsetX: config.isRtl ? 5 : -5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
options={options}
|
||||||
|
series={series}
|
||||||
|
type="area"
|
||||||
|
height={height}
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StackedAreaChart;
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
"use client";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||||
|
import { colors } from "@/lib/colors";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { hexToRGB } from "@/lib/utils";
|
||||||
|
import { useConfig } from "@/hooks/use-config";
|
||||||
|
|
||||||
|
const BarImageChart = ({ height = 350 }) => {
|
||||||
|
const [config] = useConfig();
|
||||||
|
const { theme: mode } = useTheme();
|
||||||
|
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
name: "coins",
|
||||||
|
data: [
|
||||||
|
2, 4, 3, 4, 3, 5, 5, 6.5, 6, 5, 4, 5, 8, 7, 7, 8, 8, 10, 9, 9, 12, 12,
|
||||||
|
11, 12, 13, 14, 16, 14, 15, 17, 19, 21,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const labels: number[] = Array.from({ length: 39 }, (_, index) => index + 1);
|
||||||
|
|
||||||
|
const options: any = {
|
||||||
|
chart: {
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
horizontal: true,
|
||||||
|
barHeight: "100%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labels: labels,
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
show: false,
|
||||||
|
width: 1,
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
colors: [
|
||||||
|
colors.primary,
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
shared: false,
|
||||||
|
theme: mode === "dark" ? "dark" : "light",
|
||||||
|
y: {
|
||||||
|
formatter: function (val: number) {
|
||||||
|
return val + "K";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
position: "back",
|
||||||
|
},
|
||||||
|
|
||||||
|
fill: {
|
||||||
|
type: "image",
|
||||||
|
opacity: 0.87,
|
||||||
|
image: {
|
||||||
|
src: [
|
||||||
|
"https://apexcharts.com/wp-content/uploads/2018/10/bar-chart-image-fill.png",
|
||||||
|
],
|
||||||
|
width: 466,
|
||||||
|
height: 406,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
show: false,
|
||||||
|
axisTicks: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
categories: [2008, 2009, 2010, 2011, 2012, 2013, 2014],
|
||||||
|
axisBorder: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTicks: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
states: {
|
||||||
|
hover: {
|
||||||
|
filter: "none",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: "right",
|
||||||
|
offsetY: 40,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
options={options}
|
||||||
|
series={series}
|
||||||
|
type="bar"
|
||||||
|
height={height}
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BarImageChart;
|
||||||
|
|
@ -0,0 +1,203 @@
|
||||||
|
"use client";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||||
|
import { colors } from "@/lib/colors";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { hexToRGB } from "@/lib/utils";
|
||||||
|
import { useConfig } from "@/hooks/use-config";
|
||||||
|
import {
|
||||||
|
getGridConfig,
|
||||||
|
getYAxisConfig,
|
||||||
|
} from "@/lib/appex-chart-options";
|
||||||
|
|
||||||
|
const BarsWithMarkes = ({ height = 350 }) => {
|
||||||
|
const [config] = useConfig();
|
||||||
|
const { theme: mode } = useTheme();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
name: "Actual",
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
x: "2011",
|
||||||
|
y: 12,
|
||||||
|
goals: [
|
||||||
|
{
|
||||||
|
name: "Expected",
|
||||||
|
value: 14,
|
||||||
|
strokeWidth: 2,
|
||||||
|
strokeDashArray: 2,
|
||||||
|
strokeColor: "#775DD0",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "2012",
|
||||||
|
y: 44,
|
||||||
|
goals: [
|
||||||
|
{
|
||||||
|
name: "Expected",
|
||||||
|
value: 54,
|
||||||
|
strokeWidth: 5,
|
||||||
|
strokeHeight: 10,
|
||||||
|
strokeColor: "#775DD0",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "2013",
|
||||||
|
y: 54,
|
||||||
|
goals: [
|
||||||
|
{
|
||||||
|
name: "Expected",
|
||||||
|
value: 52,
|
||||||
|
strokeWidth: 10,
|
||||||
|
strokeHeight: 0,
|
||||||
|
strokeLineCap: "round",
|
||||||
|
strokeColor: "#775DD0",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "2014",
|
||||||
|
y: 66,
|
||||||
|
goals: [
|
||||||
|
{
|
||||||
|
name: "Expected",
|
||||||
|
value: 61,
|
||||||
|
strokeWidth: 10,
|
||||||
|
strokeHeight: 0,
|
||||||
|
strokeLineCap: "round",
|
||||||
|
strokeColor: "#775DD0",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "2015",
|
||||||
|
y: 81,
|
||||||
|
goals: [
|
||||||
|
{
|
||||||
|
name: "Expected",
|
||||||
|
value: 66,
|
||||||
|
strokeWidth: 10,
|
||||||
|
strokeHeight: 0,
|
||||||
|
strokeLineCap: "round",
|
||||||
|
strokeColor: "#775DD0",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: "2016",
|
||||||
|
y: 67,
|
||||||
|
goals: [
|
||||||
|
{
|
||||||
|
name: "Expected",
|
||||||
|
value: 70,
|
||||||
|
strokeWidth: 5,
|
||||||
|
strokeHeight: 10,
|
||||||
|
strokeColor: "#775DD0",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const options: any = {
|
||||||
|
chart: {
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
horizontal: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
formatter: function (val: number, opt: any) {
|
||||||
|
const goals =
|
||||||
|
opt.w.config.series[opt.seriesIndex].data[opt.dataPointIndex].goals;
|
||||||
|
|
||||||
|
if (goals && goals.length) {
|
||||||
|
return `${val} / ${goals[0].value}`;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
show: false,
|
||||||
|
width: 1,
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
colors: [
|
||||||
|
colors.success,
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
theme: mode === "dark" ? "dark" : "light",
|
||||||
|
},
|
||||||
|
grid: getGridConfig(),
|
||||||
|
yaxis: getYAxisConfig(
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
),
|
||||||
|
xaxis: {
|
||||||
|
axisBorder: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTicks: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
legend: {
|
||||||
|
show: true,
|
||||||
|
showForSingleSeries: true,
|
||||||
|
customLegendItems: ["Actual", "Expected"],
|
||||||
|
labels: {
|
||||||
|
colors: mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 5,
|
||||||
|
vertical: 5,
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
radius: 10,
|
||||||
|
offsetX: config.isRtl ? 5 : -5,
|
||||||
|
fillColors: [
|
||||||
|
colors.success,
|
||||||
|
colors.primary,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
options={options}
|
||||||
|
series={series}
|
||||||
|
type="bar"
|
||||||
|
height={height}
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BarsWithMarkes;
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
"use client";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||||
|
import { colors } from "@/lib/colors";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { hexToRGB } from "@/lib/utils";
|
||||||
|
import { useConfig } from "@/hooks/use-config";
|
||||||
|
import {
|
||||||
|
getGridConfig,
|
||||||
|
getLabel,
|
||||||
|
getYAxisConfig,
|
||||||
|
} from "@/lib/appex-chart-options";
|
||||||
|
|
||||||
|
const BasicBar = ({ height = 350 }) => {
|
||||||
|
const [config] = useConfig();
|
||||||
|
const { theme: mode } = useTheme();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
data: [400, 430, 448, 470, 540, 580, 690, 1100, 1200, 1380],
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const options: any = {
|
||||||
|
chart: {
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: "smooth",
|
||||||
|
width: 4,
|
||||||
|
},
|
||||||
|
colors: [colors.info],
|
||||||
|
tooltip: {
|
||||||
|
theme: mode === "dark" ? "dark" : "light",
|
||||||
|
},
|
||||||
|
grid: getGridConfig(),
|
||||||
|
yaxis: getYAxisConfig(
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
),
|
||||||
|
xaxis: {
|
||||||
|
categories: [
|
||||||
|
"South Korea",
|
||||||
|
"Canada",
|
||||||
|
"United Kingdom",
|
||||||
|
"Netherlands",
|
||||||
|
"Italy",
|
||||||
|
"France",
|
||||||
|
"Japan",
|
||||||
|
"United States",
|
||||||
|
"China",
|
||||||
|
"Germany",
|
||||||
|
],
|
||||||
|
labels: getLabel(mode === 'light' ? colors["default-600"] : colors["default-300"]),
|
||||||
|
axisBorder: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTicks: {
|
||||||
|
show: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
horizontal: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
options={options}
|
||||||
|
series={series}
|
||||||
|
type="bar"
|
||||||
|
height={height}
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BasicBar;
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
"use client";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||||
|
import { colors } from "@/lib/colors";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { hexToRGB } from "@/lib/utils";
|
||||||
|
import { useConfig } from "@/hooks/use-config";
|
||||||
|
import { getGridConfig, getLabel } from "@/lib/appex-chart-options";
|
||||||
|
|
||||||
|
const CustomLabelBar = ({ height = 350 }) => {
|
||||||
|
const [config] = useConfig();
|
||||||
|
const { theme: mode } = useTheme();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
data: [400, 430, 448, 470, 540, 580, 690, 1100, 1200, 1380],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const options: any = {
|
||||||
|
chart: {
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
barHeight: "100%",
|
||||||
|
distributed: true,
|
||||||
|
horizontal: true,
|
||||||
|
dataLabels: {
|
||||||
|
position: "bottom",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: true,
|
||||||
|
textAnchor: "start",
|
||||||
|
style: {
|
||||||
|
colors: ["#fff"]
|
||||||
|
},
|
||||||
|
formatter: function (val: number, opt: any) {
|
||||||
|
return opt.w.globals.labels[opt.dataPointIndex] + ": " + val;
|
||||||
|
},
|
||||||
|
offsetX: 0,
|
||||||
|
dropShadow: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
show: false,
|
||||||
|
width: 1,
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
colors: [
|
||||||
|
colors.primary,
|
||||||
|
colors.info,
|
||||||
|
colors.success,
|
||||||
|
colors.primary
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
theme: mode === "dark" ? "dark" : "light",
|
||||||
|
},
|
||||||
|
grid: getGridConfig(),
|
||||||
|
yaxis: {
|
||||||
|
show: false,
|
||||||
|
axisTicks: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
categories: [
|
||||||
|
"South Korea",
|
||||||
|
"Canada",
|
||||||
|
"United Kingdom",
|
||||||
|
"Netherlands",
|
||||||
|
"Italy",
|
||||||
|
"France",
|
||||||
|
"Japan",
|
||||||
|
"United States",
|
||||||
|
"China",
|
||||||
|
"India",
|
||||||
|
],
|
||||||
|
axisBorder: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTicks: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
labels: getLabel(mode === 'light' ? colors["default-600"] : colors["default-300"]),
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
colors: mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 5,
|
||||||
|
vertical: 5,
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
radius: 10,
|
||||||
|
offsetX: config.isRtl ? 5 : -5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
options={options}
|
||||||
|
series={series}
|
||||||
|
type="bar"
|
||||||
|
height={height}
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomLabelBar;
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
"use client";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||||
|
import { colors } from "@/lib/colors";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { hexToRGB } from "@/lib/utils";
|
||||||
|
import { useConfig } from "@/hooks/use-config";
|
||||||
|
import {
|
||||||
|
getGridConfig,
|
||||||
|
getLabel,
|
||||||
|
getYAxisConfig,
|
||||||
|
} from "@/lib/appex-chart-options";
|
||||||
|
|
||||||
|
const GroupedBar = ({ height = 350 }) => {
|
||||||
|
const [config] = useConfig();
|
||||||
|
const { theme: mode } = useTheme();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
data: [44, 55, 41, 64, 22, 43],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: [53, 32, 33, 52, 13, 44],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const options: any = {
|
||||||
|
chart: {
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
horizontal: true,
|
||||||
|
dataLabels: {
|
||||||
|
position: "top",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: true,
|
||||||
|
offsetX: -10,
|
||||||
|
style: {
|
||||||
|
fontSize: "12px",
|
||||||
|
fontWeight: 700,
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
show: false,
|
||||||
|
width: 1,
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
colors: [
|
||||||
|
colors.primary,
|
||||||
|
colors.info,
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
theme: mode === "dark" ? "dark" : "light",
|
||||||
|
},
|
||||||
|
grid: getGridConfig(),
|
||||||
|
yaxis: getYAxisConfig(
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
),
|
||||||
|
xaxis: {
|
||||||
|
categories: [2001, 2002, 2003, 2004, 2005, 2006],
|
||||||
|
axisBorder: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTicks: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
labels: getLabel(
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
),
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
colors: mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 5,
|
||||||
|
vertical: 5,
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
radius: 10,
|
||||||
|
offsetX: config.isRtl ? 5 : -5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
options={options}
|
||||||
|
series={series}
|
||||||
|
type="bar"
|
||||||
|
height={height}
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GroupedBar;
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
"use client";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||||
|
import { colors } from "@/lib/colors";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { hexToRGB } from "@/lib/utils";
|
||||||
|
import { useConfig } from "@/hooks/use-config";
|
||||||
|
import { getGridConfig, getYAxisConfig } from "@/lib/appex-chart-options";
|
||||||
|
|
||||||
|
const GroupedStackBar = ({ height = 350 }) => {
|
||||||
|
const [config] = useConfig();
|
||||||
|
const { theme: mode } = useTheme();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
name: "Q1 Budget",
|
||||||
|
group: "budget",
|
||||||
|
data: [44000, 55000, 41000, 67000, 22000],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Q1 Actual",
|
||||||
|
group: "actual",
|
||||||
|
data: [48000, 50000, 40000, 65000, 25000],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Q2 Budget",
|
||||||
|
group: "budget",
|
||||||
|
data: [13000, 36000, 20000, 8000, 13000],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Q2 Actual",
|
||||||
|
group: "actual",
|
||||||
|
data: [20000, 40000, 25000, 10000, 12000],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const options: any = {
|
||||||
|
chart: {
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
stacked: true,
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
horizontal: true,
|
||||||
|
dataLabels: {
|
||||||
|
total: {
|
||||||
|
enabled: false,
|
||||||
|
offsetX: 0,
|
||||||
|
style: {
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
],
|
||||||
|
fontSize: "13px",
|
||||||
|
fontWeight: 800,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: true,
|
||||||
|
offsetX: 0,
|
||||||
|
style: {
|
||||||
|
fontSize: "12px",
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
formatter: (val: number) => {
|
||||||
|
return val / 1000 + "K";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
show: false,
|
||||||
|
width: 1,
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
]
|
||||||
|
},
|
||||||
|
colors: [
|
||||||
|
colors.primary,
|
||||||
|
colors.info,
|
||||||
|
colors.success,
|
||||||
|
colors.primary
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
theme: mode === "dark" ? "dark" : "light",
|
||||||
|
},
|
||||||
|
grid: getGridConfig(),
|
||||||
|
yaxis: getYAxisConfig(
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
),
|
||||||
|
xaxis: {
|
||||||
|
categories: [
|
||||||
|
"Online advertising",
|
||||||
|
"Sales Training",
|
||||||
|
"Print advertising",
|
||||||
|
"Catalogs",
|
||||||
|
"Meetings",
|
||||||
|
],
|
||||||
|
axisBorder: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTicks: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
formatter: function (val: number) {
|
||||||
|
return val + "K";
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
legend: {
|
||||||
|
position: "top",
|
||||||
|
horizontalAlign: "left",
|
||||||
|
offsetX: 40,
|
||||||
|
labels: {
|
||||||
|
colors: mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 5,
|
||||||
|
vertical: 5,
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
radius: 2,
|
||||||
|
offsetX: config.isRtl ? 5 : -5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
options={options}
|
||||||
|
series={series}
|
||||||
|
type="bar"
|
||||||
|
height={height}
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GroupedStackBar;
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
export const metadata = {
|
||||||
|
title: "Appex Bar Chart",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
"use client";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||||
|
import { colors } from "@/lib/colors";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { hexToRGB } from "@/lib/utils";
|
||||||
|
import { useConfig } from "@/hooks/use-config";
|
||||||
|
import {
|
||||||
|
getGridConfig,
|
||||||
|
getYAxisConfig,
|
||||||
|
} from "@/lib/appex-chart-options";
|
||||||
|
|
||||||
|
const NegativeValuesBar = ({ height = 350 }) => {
|
||||||
|
const [config] = useConfig();
|
||||||
|
const { theme: mode } = useTheme();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
name: "Males",
|
||||||
|
data: [
|
||||||
|
0.4, 0.65, 0.76, 0.88, 1.5, 2.1, 2.9, 3.8, 3.9, 4.2, 4, 4.3, 4.1, 4.2,
|
||||||
|
4.5, 3.9, 3.5, 3,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Females",
|
||||||
|
data: [
|
||||||
|
-0.8, -1.05, -1.06, -1.18, -1.4, -2.2, -2.85, -3.7, -3.96, -4.22, -4.3,
|
||||||
|
-4.4, -4.1, -4, -4.1, -3.4, -3.1, -2.8,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const options: any = {
|
||||||
|
chart: {
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
stacked: true,
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
horizontal: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false,
|
||||||
|
offsetX: 0,
|
||||||
|
style: {
|
||||||
|
fontSize: "12px",
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
show: false,
|
||||||
|
width: 1,
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
colors: [
|
||||||
|
colors.primary,
|
||||||
|
colors.info,
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
theme: mode === "dark" ? "dark" : "light",
|
||||||
|
},
|
||||||
|
grid: getGridConfig(),
|
||||||
|
yaxis: getYAxisConfig(
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"]
|
||||||
|
),
|
||||||
|
xaxis: {
|
||||||
|
categories: [
|
||||||
|
"85+",
|
||||||
|
"80-84",
|
||||||
|
"75-79",
|
||||||
|
"70-74",
|
||||||
|
"65-69",
|
||||||
|
"60-64",
|
||||||
|
"55-59",
|
||||||
|
"50-54",
|
||||||
|
"45-49",
|
||||||
|
"40-44",
|
||||||
|
"35-39",
|
||||||
|
"30-34",
|
||||||
|
"25-29",
|
||||||
|
"20-24",
|
||||||
|
"15-19",
|
||||||
|
"10-14",
|
||||||
|
"5-9",
|
||||||
|
"0-4",
|
||||||
|
],
|
||||||
|
axisBorder: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTicks: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
formatter: function (val: number) {
|
||||||
|
return Math.abs(Math.round(val)) + "%";
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
colors: [
|
||||||
|
mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
legend: {
|
||||||
|
position: "top",
|
||||||
|
horizontalAlign: "end",
|
||||||
|
offsetX: 40,
|
||||||
|
labels: {
|
||||||
|
colors: mode === 'light' ? colors["default-600"] : colors["default-300"],
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 5,
|
||||||
|
vertical: 5,
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
radius: 2,
|
||||||
|
offsetX: config.isRtl ? 5 : -5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
options={options}
|
||||||
|
series={series}
|
||||||
|
type="bar"
|
||||||
|
height={height}
|
||||||
|
width={"100%"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NegativeValuesBar;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue