Initial Commit

This commit is contained in:
hanif salafi 2024-11-26 10:09:48 +07:00
commit 1cdfc5bad5
1313 changed files with 124079 additions and 0 deletions

3
.eslintrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

37
.gitignore vendored Normal file
View File

@ -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

14
.lighthouserc.json Normal file
View File

@ -0,0 +1,14 @@
{
"ci": {
"collect": {
"staticDistDir": "./public"
},
"assert": {
"assertions": {}
},
"upload": {
"target": "temporary-public-storage"
}
}
}

36
README.md Normal file
View File

@ -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.

12
action/app-actions.ts Normal file
View File

@ -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;
}

16
action/auth-action.ts Normal file
View File

@ -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);
}
};

View File

@ -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;

View File

@ -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]

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,9 @@
export const metadata = {
title: "Calender",
};
const Layout = ({ children }: { children: React.ReactNode }) => {
return <>{children}</>;
};
export default Layout;

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -0,0 +1,9 @@
import Blank from "./components/blank-chat"
const ChatPage = async () => {
return (
<Blank />
)
}
export default ChatPage

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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];

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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)
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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]

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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)[]

View File

@ -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

View File

@ -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;

View File

@ -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]

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -0,0 +1,9 @@
import React from 'react'
const Loading = () => {
return (
<div>loading..</div>
)
}
export default Loading

View File

@ -0,0 +1,9 @@
import { redirect } from '@/components/navigation'
const ProjectPage = () => {
redirect('/app/projects/grid')
return null
}
export default ProjectPage

View File

@ -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;

View File

@ -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;

View File

@ -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]

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,10 @@
export const metadata = {
title: "Appex Area Chart",
};
const Layout = ({ children }: { children: React.ReactNode }) => {
return <>{children}</>;
};
export default Layout;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,9 @@
export const metadata = {
title: "Appex Bar Chart",
};
const Layout = ({ children }: { children: React.ReactNode }) => {
return <>{children}</>;
};
export default Layout;

View File

@ -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