Skip to content

Commit ac4577a

Browse files
committed
Merge branch 'develop'
2 parents c690df7 + 773f961 commit ac4577a

16 files changed

Lines changed: 652 additions & 41 deletions

File tree

bun.lock

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
"test": "npx tailwindcss init"
1313
},
1414
"dependencies": {
15+
"@hello-pangea/dnd": "^18.0.1",
1516
"@tanstack/react-query": "5.66.0",
1617
"add": "2.0.6",
1718
"axios": "1.7.9",
1819
"chart.js": "^4.4.7",
1920
"jalali-moment": "3.3.11",
21+
"moment": "^2.30.1",
22+
"moment-hijri": "^3.0.0",
2023
"motion": "12.3.1",
2124
"ms": "2.1.3",
2225
"react": "18.3.1",
@@ -33,6 +36,7 @@
3336
"@biomejs/biome": "1.9.4",
3437
"@eslint/js": "9.17.0",
3538
"@tailwindcss/vite": "4.0.0",
39+
"@types/moment-hijri": "^2.1.4",
3640
"@types/ms": "2.1.0",
3741
"@types/node": "22.13.1",
3842
"@types/react": "18.2.19",

src/common/constant/store.key.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@ export enum StoreKey {
44
CURRENCY_UPDATED_AT = 'CURRENCY_UPDATED_AT',
55
SELECTED_CITY = 'SELECTED_CITY',
66
CURRENT_WEATHER = 'CURRENT_WEATHER',
7+
LAYOUT_ORDER = 'LAYOUT_ORDER',
8+
Todos = 'Todos',
79
}
810
export type StoreKeyType = StoreKey | `currency:${string}`

src/context/todo.context.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { type ReactNode, createContext, useContext, useEffect, useState } from 'react'
2+
import { StoreKey } from '../common/constant/store.key'
3+
import { getFromStorage, setToStorage } from '../common/storage'
4+
import type { Todo } from '../layouts/calendar/interface/todo.interface'
5+
6+
interface TodoContextType {
7+
todos: Todo[]
8+
addTodo: (text: string, date: string) => void
9+
removeTodo: (id: string) => void
10+
toggleTodo: (id: string) => void
11+
setTodos: (todos: Todo[]) => void
12+
}
13+
14+
const TodoContext = createContext<TodoContextType | undefined>(undefined)
15+
export function TodoProvider({ children }: { children: ReactNode }) {
16+
const [todos, setTodos] = useState<Todo[]>([])
17+
18+
useEffect(() => {
19+
const todosFromStorage = getFromStorage(StoreKey.Todos)
20+
if (todosFromStorage) {
21+
setTodos(todosFromStorage as Todo[])
22+
}
23+
}, [])
24+
25+
const addTodo = (text: string, date: string) => {
26+
const todoList = [
27+
...todos,
28+
{ id: Math.random().toString(36).slice(2), text, completed: false, date },
29+
]
30+
31+
setTodos(todoList)
32+
33+
setToStorage(StoreKey.Todos, todoList)
34+
}
35+
36+
const removeTodo = (id: string) => {
37+
setTodos(todos.filter((todo) => todo.id !== id))
38+
}
39+
40+
const toggleTodo = (id: string) => {
41+
setTodos(
42+
todos.map((todo) =>
43+
todo.id === id ? { ...todo, completed: !todo.completed } : todo,
44+
),
45+
)
46+
}
47+
48+
return (
49+
<TodoContext.Provider value={{ todos, addTodo, removeTodo, toggleTodo, setTodos }}>
50+
{children}
51+
</TodoContext.Provider>
52+
)
53+
}
54+
55+
export function useTodo() {
56+
const context = useContext(TodoContext)
57+
if (context === undefined) {
58+
throw new Error('useTodo must be used within a TodoProvider')
59+
}
60+
return context
61+
}

src/layouts/calendar/calendar.tsx

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import jalaliMoment from 'jalali-moment'
2+
import type React from 'react'
3+
import { useState } from 'react'
4+
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa6'
5+
import { TodoProvider, useTodo } from '../../context/todo.context'
6+
import { useGetEvents } from '../../services/getMethodHooks/getEvents.hook'
7+
import { DayItem } from './components/day'
8+
import { Events } from './components/events/event'
9+
import { Todos } from './components/todos/todos'
10+
import { formatDateStr } from './utils'
11+
12+
const PERSIAN_MONTHS = [
13+
'فروردین',
14+
'اردیبهشت',
15+
'خرداد',
16+
'تیر',
17+
'مرداد',
18+
'شهریور',
19+
'مهر',
20+
'آبان',
21+
'آذر',
22+
'دی',
23+
'بهمن',
24+
'اسفند',
25+
]
26+
27+
const WEEKDAYS = ['شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنج‌شنبه', 'جمعه']
28+
29+
export const PersianCalendar: React.FC = () => {
30+
const today = jalaliMoment()
31+
const [currentDate, setCurrentDate] = useState(today)
32+
const [selectedDate, setSelectedDate] = useState(today.clone())
33+
34+
const firstDayOfMonth = currentDate.clone().startOf('jMonth').day()
35+
36+
const { data: events } = useGetEvents()
37+
38+
const daysInMonth = currentDate.clone().endOf('jMonth').jDate()
39+
40+
const emptyDays = (firstDayOfMonth + 1) % 7
41+
42+
const changeMonth = (delta: number) => {
43+
setCurrentDate((prev) => prev.clone().add(delta, 'jMonth'))
44+
}
45+
46+
const selectedDateStr = formatDateStr(selectedDate)
47+
48+
const { todos } = useTodo()
49+
return (
50+
<div className="grid gap-4 md:grid-cols-5" dir="rtl">
51+
<div className="p-4 md:col-span-3 bg-gradient-to-br from-neutral-100 to-neutral-50 dark:from-neutral-800 dark:to-neutral-900 rounded-xl backdrop-blur-sm lg:h-96">
52+
<div className="flex items-center justify-between mb-4">
53+
<h3 className="text-xl font-medium text-gray-200">
54+
{PERSIAN_MONTHS[currentDate.jMonth()]} {currentDate.jYear()}
55+
</h3>
56+
<div className="flex gap-2">
57+
<button
58+
onClick={() => changeMonth(-1)}
59+
className="flex flex-row-reverse items-center gap-1 p-2 text-gray-400 rounded-lg hover:text-gray-200 hover:bg-gray-700/50"
60+
>
61+
ماه قبل
62+
<FaChevronRight />
63+
</button>
64+
<button
65+
onClick={() => changeMonth(1)}
66+
className="flex flex-row-reverse items-center gap-1 p-2 text-gray-400 rounded-lg hover:text-gray-200 hover:bg-gray-700/50"
67+
>
68+
<FaChevronLeft />
69+
ماه بعد
70+
</button>
71+
</div>
72+
</div>
73+
74+
<div className="grid grid-cols-7 gap-2">
75+
{WEEKDAYS.map((day) => (
76+
<div key={day} className="py-2 text-xs text-center text-gray-400">
77+
{day}
78+
</div>
79+
))}
80+
81+
{Array.from({ length: emptyDays }).map((_, i) => (
82+
<div key={`empty-${i}`} className="p-2" />
83+
))}
84+
85+
{Array.from({ length: daysInMonth }, (_, i) => {
86+
return (
87+
<DayItem
88+
currentDate={currentDate}
89+
day={i + 1}
90+
events={events}
91+
selectedDateStr={selectedDateStr}
92+
setSelectedDate={setSelectedDate}
93+
todos={todos}
94+
key={i + 1}
95+
/>
96+
)
97+
})}
98+
</div>
99+
</div>
100+
101+
<div className="p-4 md:col-span-2 bg-gradient-to-br from-neutral-100 to-neutral-50 dark:from-neutral-800 dark:to-neutral-900 rounded-xl backdrop-blur-sm">
102+
<h3 className="mb-4 text-xl font-medium text-gray-200">
103+
{PERSIAN_MONTHS[selectedDate.jMonth()]} {selectedDate.jDate()}
104+
</h3>
105+
106+
{/* todos */}
107+
<Todos currentDate={selectedDate} />
108+
109+
{/* events */}
110+
<Events events={events} currentDate={selectedDate} />
111+
</div>
112+
</div>
113+
)
114+
}
115+
116+
const CalendarLayout = () => {
117+
return (
118+
<section className="p-2 mx-1 overflow-y-auto rounded lg:mx-4 max-h-[calc(100vh-4rem)]">
119+
<div className="flex items-center justify-between w-full px-1 mb-4">
120+
<h2 className="text-lg font-semibold text-gray-200 font-[balooTamma]">
121+
📅 Calender
122+
</h2>
123+
<div
124+
className="text-xs text-gray-400 font-[balooTamma] font-semibold flex items-center gap-1
125+
hover:text-gray-300 cursor-pointer"
126+
>
127+
<span>-</span>
128+
</div>
129+
</div>
130+
<TodoProvider>
131+
<PersianCalendar />
132+
</TodoProvider>
133+
</section>
134+
)
135+
}
136+
137+
export default CalendarLayout

0 commit comments

Comments
 (0)