Skip to content

add filter, enhanced fetching, enhanced mobile experience #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"qrcode": "^1.5.4",
"react": "^18",
"react-dom": "^18",
"react-select": "^5.8.1",
"sweetalert2": "^11.14.2",
"sweetalert2-react-content": "^5.0.7"
},
Expand Down
13 changes: 12 additions & 1 deletion src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,27 @@ import Search from './todo/popup/Search';
import AddTodoPopup from './todo/popup/AddTodo';
import { useRouter } from 'next/router';
import ProfilePopup from './todo/popup/ProfilePopup';
import Filter from './todo/popup/Filter';

const Header = () => {
interface HeaderProps {
setFilterStatus: (status: string) => void;
filterStatus: string;
}

const Header = ({ setFilterStatus, filterStatus }: HeaderProps) => {
const [showDropdown, setShowDropdown] = useState(false);
const [showSearch, setShowSearch] = useState(false);
const [showAddTodo, setShowAddTodo] = useState(false);
const [showProfile, setShowProfile] = useState(false);
const [showFilter, setShowFilter] = useState(false);
const router = useRouter();
const currentLocation = router.pathname;

const toggleDropdown = () => setShowDropdown(!showDropdown);
const toggleSearch = () => setShowSearch(!showSearch);
const toggleAddTodo = () => setShowAddTodo(!showAddTodo);
const toggleProfile = () => setShowProfile(!showProfile);
const toggleFilter = () => setShowFilter(!showFilter);

return (
<div className="h-16 w-full dark:bg-[#1a1a1a] bg-slate-100 rounded-lg items-center flex p-4 sticky top-0 z-50">
Expand All @@ -28,6 +36,7 @@ const Header = () => {
<div className="flex items-center">
<Icon icon="mi:add" className="ml-3 lg:hidden text-2xl cursor-pointer" onClick={toggleAddTodo} />
<Icon icon="material-symbols:search" className="ml-3 text-2xl cursor-pointer" onClick={toggleSearch} />
<Icon icon={!filterStatus ? 'heroicons-outline:filter' : 'heroicons-solid:filter'} className="ml-3 text-2xl font-black cursor-pointer stroke-2" onClick={toggleFilter} />
</div>
)}
<Icon icon="solar:user-bold" className="ml-3 text-2xl cursor-pointer" onClick={toggleDropdown} />
Expand All @@ -40,9 +49,11 @@ const Header = () => {
</ul>
</div>
)}

<Search show={showSearch} onClose={toggleSearch} />
<AddTodoPopup show={showAddTodo} onClose={toggleAddTodo} />
<ProfilePopup show={showProfile} onClose={toggleProfile} />
<Filter show={showFilter} onClose={toggleFilter} setFilterStatus={setFilterStatus} filterStatus={filterStatus} />
</div>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/calendar/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const Calendar: React.FC<CalendarProps> = ({ todoData, onDateClick }) => {
};

return (
<div className={`p-4 h-1/2 rounded-3xl shadow-lg w-full ${pinkMode ? 'bg-pink-200' : 'bg-blue-200'}`}>
<div className={`p-4 h-full rounded-3xl shadow-lg w-full ${pinkMode ? 'bg-pink-200' : 'bg-blue-200'}`}>
<div className="flex justify-between items-center mb-4">
<h2
className="text-xl font-semibold text-indigo-800 cursor-pointer"
Expand Down
2 changes: 1 addition & 1 deletion src/components/todo/ActivityDesktopTodo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const ActivityDesktopTodo: React.FC<ActivityDesktopTodoProps> = ({ todoData, sel
});

return (
<div className="max-h-[95vh] md:min-h-[96vh] xl:min-h-[90.5vh] flex flex-col w-full max-w-md rounded-xl bg-slate-50 dark:bg-gray-800 p-6 overflow-hidden">
<div className="max-h-[45vh] h-[35dvh] min-h-[35dvh] lg:min-h-full flex flex-col w-full max-w-lg rounded-xl bg-slate-50 dark:bg-gray-800 p-6 overflow-hidden">
<div className="flex items-center justify-between mb-4">
<h1 className="text-2xl font-poppins font-bold">To-do</h1>
<p className="text-sm text-gray-500 dark:text-gray-400">bro.. you have {todoData.length} tasks</p>
Expand Down
46 changes: 38 additions & 8 deletions src/components/todo/ActivityTodo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,9 @@ const ActivityTodo: React.FC<ActivityTodoProps> = ({ todoData, selectedDate }) =
const endDate = parseISO(task.end);

const addToGroup = (date: Date, label: string) => {
let key = label;
if (!grouped[key]) grouped[key] = [];
if (!grouped[key].some(t => t.id === task.id)) {
grouped[key].push(task);
if (!grouped[label]) grouped[label] = [];
if (!grouped[label].some(t => t.id === task.id)) {
grouped[label].push(task);
}
};

Expand All @@ -69,15 +68,13 @@ const ActivityTodo: React.FC<ActivityTodoProps> = ({ todoData, selectedDate }) =
) {
addToGroup(currentDate, 'Today');
}

if (
(isBefore(startDate, startOfTomorrow) && isAfter(endDate, startOfTomorrow)) ||
(isEqual(startDate, startOfTomorrow) || isEqual(endDate, startOfTomorrow)) ||
(isAfter(startDate, startOfTomorrow) && isBefore(startDate, endOfTomorrow))
) {
addToGroup(addDays(currentDate, 1), 'Tomorrow');
}

let currentCheckDate = isAfter(startDate, startOfToday) ? startDate : startOfToday;
while (isBefore(currentCheckDate, endDate) || isEqual(currentCheckDate, endDate)) {
const label = isToday(currentCheckDate)
Expand All @@ -93,13 +90,46 @@ const ActivityTodo: React.FC<ActivityTodoProps> = ({ todoData, selectedDate }) =
}
});

return grouped;
const sortedKeys = Object.keys(grouped).sort((a, b) => {
const order = ['Today', 'Tomorrow', 'Past'];
const aIndex = order.indexOf(a);
const bIndex = order.indexOf(b);

// sooooo, 1 is like go down, -1 go up (in the list). phm kan? xixixi (for ur future reference ig)
const getStatusOrder = (key: string) => {
const tasks = grouped[key];
if (tasks.some(task => task.status === 'Belum Selesai')) return -1;
if (tasks.some(task => task.status === 'Dalam Progress')) return -1;
if (tasks.some(task => task.status === 'Selesai')) return 1;
return 0;
};

if (a === 'Past' && getStatusOrder(a) === -1) return -1;
if (b === 'Past' && getStatusOrder(b) === -1) return 1;

if (aIndex !== -1 && bIndex !== -1) return aIndex - bIndex;
if (aIndex !== -1) return -1;
if (bIndex !== -1) return 1;

const statusOrderA = getStatusOrder(a);
const statusOrderB = getStatusOrder(b);
if (statusOrderA !== statusOrderB) return statusOrderA - statusOrderB;

return a.localeCompare(b);
});

const sortedGrouped: { [key: string]: Task[] } = {};
sortedKeys.forEach(key => {
sortedGrouped[key] = grouped[key];
});

return sortedGrouped;
};

const groupedTasks = groupTasksByDate();

return (
<div className="dark:bg-gray-900 bg-[#f6f6f6] text-white p-6 rounded-3xl shadow-lg min-w-full mx-auto h-1/2 min-h-[50vh] overflow-y-auto sigma-uwu-scrollbar">
<div className="dark:bg-gray-900 bg-[#f6f6f6] text-white p-6 rounded-3xl shadow-lg min-w-full mx-auto min-h-[45dvh] max-h-[45dvh] lg:min-h-full overflow-y-auto sigma-uwu-scrollbar">
<ViewTodo todo_id={todoId} show={showTodoDetailPopup} onClose={toggleTodoDetailPopup} />
{Object.entries(groupedTasks).length === 0 ? (
<div className="flex justify-center items-center h-full">
Expand Down
2 changes: 1 addition & 1 deletion src/components/todo/WordsOfAffirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const AffirmationDisplay = () => {

return (
<motion.div
className="h-72 min-h-1/2 md:h-1/2 flex items-center justify-center p-8 cursor-pointer rounded-lg shadow-lg"
className="h-full min-h-[35dvh] lg:min-h-full md:h-1/2 flex items-center justify-center p-8 cursor-pointer rounded-lg shadow-lg"
style={{
background: `linear-gradient(135deg, ${gradientColors[0]}, ${gradientColors[1]})`,
}}
Expand Down
121 changes: 121 additions & 0 deletions src/components/todo/popup/Filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React, { useState } from 'react';
import Select from 'react-select';

interface FilterProps {
setFilterStatus: (status: string) => void;
filterStatus: string;
show: boolean;
onClose: () => void;
}

interface Option {
value: string;
label: string;
}

const Filter: React.FC<FilterProps> = ({ setFilterStatus, filterStatus, show, onClose }) => {
const [selectedOption, setSelectedOption] = useState<Option | null>(
filterStatus ? { value: filterStatus, label: filterStatus } : null
);

if (!show) return null;

const options: Option[] = [
{ value: 'Selesai', label: 'Selesai' },
{ value: 'Belum Selesai', label: 'Belum Selesai' },
{ value: 'Dalam Progress', label: 'Dalam Progress' }
];

const handleChange = (selected: Option | null) => {
setSelectedOption(selected);
setFilterStatus(selected ? selected.value : '');
};

const handleClear = () => {
setSelectedOption(null);
setFilterStatus('');
};

return (
<div className="fixed inset-0 z-50 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div className="fixed inset-0 bg-black opacity-50" onClick={onClose}></div>
<div className="flex items-center justify-center min-h-screen p-4">
<div className="bg-white dark:bg-gray-900 w-full max-w-sm rounded-lg shadow-xl overflow-hidden relative z-10" style={{ minHeight: '35dvh', height: '35dvh' }}>
<div className="p-4 h-full flex flex-col justify-between">
<div>
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-4">Filter to-do</h3>
<Select
options={options}
value={selectedOption}
onChange={handleChange}
placeholder="Select status"
className="w-full mb-4"
classNamePrefix="react-select"
theme={(theme) => ({
...theme,
colors: {
...theme.colors,
primary: '#3b82f6',
primary75: '#60a5fa',
primary50: '#93c5fd',
primary25: '#bfdbfe',
neutral0: 'var(--color-bg)',
neutral80: 'var(--color-text)',
neutral20: 'var(--color-border)',
},
})}
styles={{
control: (provided, state) => ({
...provided,
backgroundColor: 'var(--color-bg)',
borderColor: state.isFocused ? '#3b82f6' : 'var(--color-border)',
'&:hover': {
borderColor: '#3b82f6',
},
color: 'var(--color-text)',
}),
option: (provided, state) => ({
...provided,
backgroundColor: state.isSelected
? '#3b82f6'
: state.isFocused
? '#bfdbfe'
: 'var(--color-bg)',
color: state.isSelected ? 'white' : 'var(--color-text)',
}),
singleValue: (provided) => ({
...provided,
color: 'var(--color-text)',
}),
menu: (provided) => ({
...provided,
backgroundColor: 'var(--color-bg)',
}),
}}
/>
{selectedOption && (
<button
onClick={handleClear}
className="text-sm text-blue-600 hover:text-blue-800 mb-4 block"
>
Clear selection
</button>
)}
</div>
<div className="mt-auto">
<button
type="button"
className="w-full px-4 py-2 bg-blue-600 text-white font-medium rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
onClick={onClose}
>
Close
</button>
</div>
</div>
</div>
</div>
</div>
);
};

export default Filter;
24 changes: 19 additions & 5 deletions src/pages/api/todo/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { useLocalStorage } from "@/hooks/useLocalStorage";
import pgdb from "@/services/db";
import { param } from "framer-motion/client";
import { NextApiRequest, NextApiResponse } from "next";

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') {
const sessionId = req.headers['session-id'] as string;
const date = req.query.date as string;
const status = req.query.status as string;

let params = [];

if (!sessionId) {
return res.status(401).json({ message: 'Unauthorized' });
Expand All @@ -16,14 +20,24 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
try {
let sql;
sql = 'SELECT * FROM todo WHERE session_id = (SELECT session_id FROM session WHERE session = $1)';

params.push(sessionId);
if (date) {
sql += ' AND DATE(start) <= $2 AND DATE("end") >= $2';
params.push(date);
sql += ' AND DATE(start) <= $' + params.length + ' AND DATE("end") >= $' + params.length;
}

if (status) {

const validStatuses = ['Selesai', 'Belum Selesai', 'Dalam Progress'];
if (!validStatuses.includes(status)) {
return res.status(400).json({ message: 'Invalid status' });
}
params.push(status);

sql += ' AND status = $' + params.length;
}

const result = date
? await client.query(sql, [sessionId, date])
: await client.query(sql, [sessionId]);
const result = await client.query(sql, params);

return res.status(200).json(result.rows);
} finally {
Expand Down
Loading