diff --git a/ui/src/components/Sidebar.tsx b/ui/src/components/Sidebar.tsx
index af9fc47..7c91dc3 100644
--- a/ui/src/components/Sidebar.tsx
+++ b/ui/src/components/Sidebar.tsx
@@ -2,7 +2,8 @@ import clsx from 'clsx';
import {ComponentProps} from 'react';
import {
- ArrowLeftStartOnRectangleIcon, // ArrowTrendingUpIcon,
+ ArrowLeftStartOnRectangleIcon,
+ ArrowTrendingUpIcon,
PlusIcon,
UserIcon,
UsersIcon,
@@ -81,17 +82,17 @@ export default function Sidebar(props: ComponentProps<'div'>) {
diff --git a/ui/src/components/common/Button.tsx b/ui/src/components/common/Button.tsx
index 2d141e9..07c16fc 100644
--- a/ui/src/components/common/Button.tsx
+++ b/ui/src/components/common/Button.tsx
@@ -89,6 +89,17 @@ const button = tv({
// Base
'border-red-400 data-[hovered]:border-red-500 data-[hovered]:bg-red-50',
+ // Icon
+ '[--btn-icon:theme(colors.red.500)] data-[hovered]:[--btn-icon:theme(colors.red.600)]',
+ ],
+ },
+ {
+ color: 'danger',
+ variant: 'plain',
+ class: [
+ // Base
+ 'data-[hovered]:bg-red-50 data-[pressed]:bg-red-100',
+
// Icon
'[--btn-icon:theme(colors.red.500)] data-[hovered]:[--btn-icon:theme(colors.red.600)]',
],
diff --git a/ui/src/routeTree.gen.ts b/ui/src/routeTree.gen.ts
index bc7c87e..55192fe 100644
--- a/ui/src/routeTree.gen.ts
+++ b/ui/src/routeTree.gen.ts
@@ -17,7 +17,7 @@ import { Route as AuthLoginImport } from './routes/auth/login'
import { Route as AuthForgetPassImport } from './routes/auth/forget-pass'
import { Route as DashboardGroupsImport } from './routes/_dashboard/groups'
import { Route as DashboardFriendsImport } from './routes/_dashboard/friends'
-import { Route as DashboardActivityIndexImport } from './routes/_dashboard/activity/index'
+import { Route as DashboardActivityImport } from './routes/_dashboard/activity'
import { Route as DashboardProfileMeImport } from './routes/_dashboard/profile/me'
import { Route as DashboardGroupsGroupImport } from './routes/_dashboard/groups/$group'
import { Route as DashboardFriendsFriendImport } from './routes/_dashboard/friends/$friend'
@@ -55,8 +55,8 @@ const DashboardFriendsRoute = DashboardFriendsImport.update({
getParentRoute: () => DashboardRoute,
} as any)
-const DashboardActivityIndexRoute = DashboardActivityIndexImport.update({
- path: '/activity/',
+const DashboardActivityRoute = DashboardActivityImport.update({
+ path: '/activity',
getParentRoute: () => DashboardRoute,
} as any)
@@ -76,8 +76,8 @@ const DashboardFriendsFriendRoute = DashboardFriendsFriendImport.update({
} as any)
const DashboardActivityActivityRoute = DashboardActivityActivityImport.update({
- path: '/activity/$activity',
- getParentRoute: () => DashboardRoute,
+ path: '/$activity',
+ getParentRoute: () => DashboardActivityRoute,
} as any)
// Populate the FileRoutesByPath interface
@@ -92,6 +92,10 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DashboardImport
parentRoute: typeof rootRoute
}
+ '/_dashboard/activity': {
+ preLoaderRoute: typeof DashboardActivityImport
+ parentRoute: typeof DashboardImport
+ }
'/_dashboard/friends': {
preLoaderRoute: typeof DashboardFriendsImport
parentRoute: typeof DashboardImport
@@ -110,7 +114,7 @@ declare module '@tanstack/react-router' {
}
'/_dashboard/activity/$activity': {
preLoaderRoute: typeof DashboardActivityActivityImport
- parentRoute: typeof DashboardImport
+ parentRoute: typeof DashboardActivityImport
}
'/_dashboard/friends/$friend': {
preLoaderRoute: typeof DashboardFriendsFriendImport
@@ -124,10 +128,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DashboardProfileMeImport
parentRoute: typeof DashboardImport
}
- '/_dashboard/activity/': {
- preLoaderRoute: typeof DashboardActivityIndexImport
- parentRoute: typeof DashboardImport
- }
}
}
@@ -136,11 +136,10 @@ declare module '@tanstack/react-router' {
export const routeTree = rootRoute.addChildren([
IndexRoute,
DashboardRoute.addChildren([
+ DashboardActivityRoute.addChildren([DashboardActivityActivityRoute]),
DashboardFriendsRoute.addChildren([DashboardFriendsFriendRoute]),
DashboardGroupsRoute.addChildren([DashboardGroupsGroupRoute]),
- DashboardActivityActivityRoute,
DashboardProfileMeRoute,
- DashboardActivityIndexRoute,
]),
AuthForgetPassRoute,
AuthLoginRoute,
diff --git a/ui/src/routes/_dashboard/activity.tsx b/ui/src/routes/_dashboard/activity.tsx
new file mode 100644
index 0000000..b9e198d
--- /dev/null
+++ b/ui/src/routes/_dashboard/activity.tsx
@@ -0,0 +1,178 @@
+import clsx from 'clsx';
+import {DialogTrigger} from 'react-aria-components';
+
+import {MagnifyingGlassIcon} from '@heroicons/react/24/outline';
+import {Outlet, ScrollRestoration, createFileRoute, useMatchRoute} from '@tanstack/react-router';
+import groupBy from 'just-group-by';
+
+import {Button} from '@/components/common';
+import ActivityListItem from '@/routes/_dashboard/activity/-components/ActivityListItem.tsx';
+
+export const Route = createFileRoute('/_dashboard/activity')({
+ component: ActivityLayout,
+});
+
+function ActivityLayout() {
+ const matchRoute = useMatchRoute();
+ const isRootLayout = matchRoute({to: '/activity'});
+
+ // const {data} = useApiQuery(ApiRoutes.ACTIVITY_LIST);
+ const data = {
+ results: [
+ {
+ uid: 'activity-1',
+ urn: 'urn:activity:1',
+ user: {
+ uid: 'user-1',
+ urn: 'urn:user:1',
+ fullName: 'John Doe',
+ isActive: true,
+ },
+ group: {
+ uid: 'group-1',
+ urn: 'urn:group:1',
+ name: 'Apartment Buddies',
+ },
+ template: 'Dinner',
+ description: 'Dinner at Pizza Palace',
+ target: {
+ uid: 'target-1',
+ urn: 'urn:target:1',
+ value: '35.75',
+ },
+ createdAt: '2024-03-23T18:00:00Z',
+ },
+ {
+ uid: 'activity-2',
+ urn: 'urn:activity:2',
+ user: {
+ uid: 'user-2',
+ urn: 'urn:user:2',
+ fullName: 'Jane Smith',
+ isActive: true,
+ },
+ group: {
+ uid: 'group-2',
+ urn: 'urn:group:2',
+ name: 'Weekend Getaway',
+ members: ['user-1', 'user-2', 'user-3'],
+ },
+ template: 'Groceries',
+ description: 'Shared groceries for the trip',
+ target: {
+ uid: 'target-2',
+ urn: 'urn:target:2',
+ value: '82.40',
+ },
+ createdAt: '2024-03-22T15:30:00Z',
+ },
+ {
+ uid: 'activity-3',
+ urn: 'urn:activity:3',
+ user: {
+ uid: 'user-1',
+ urn: 'urn:user:1',
+ fullName: 'John Doe',
+ isActive: true,
+ },
+ group: {
+ uid: 'group-1',
+ urn: 'urn:group:1',
+ name: 'Apartment Buddies',
+ },
+ template: 'Utilities',
+ description: 'Electricity bill for March',
+ target: {
+ uid: 'target-3',
+ urn: 'urn:target:3',
+ value: '120.00',
+ },
+ createdAt: '2024-03-20T10:00:00Z',
+ },
+ ],
+ };
+ const formatDate = (dateString: string) => {
+ const [month, year] = dateString.split('/');
+
+ const months = [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December',
+ ];
+ const monthName = months[parseInt(month, 10) - 1];
+
+ return `${monthName} ${year}`;
+ };
+
+ return (
+ <>
+
+
+
+
+
Activity
+
+
+
+
+
+
+
+
+ {Object.entries(
+ groupBy(data?.results ?? [], (activity) => {
+ const dateObj = new Date(activity.createdAt ?? '');
+ return dateObj.toLocaleDateString('en-US', {year: 'numeric', month: 'numeric'});
+ })
+ )
+ .sort((a, b) => (a[0] < b[0] ? -1 : +1))
+ .map(([date, activities]) => (
+
+
+
{formatDate(date)}
+
+
+ {activities.map((activity) => (
+
+ ))}
+
+
+ ))}
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/ui/src/routes/_dashboard/activity/$activity.tsx b/ui/src/routes/_dashboard/activity/$activity.tsx
index 0a1c0df..19481dd 100644
--- a/ui/src/routes/_dashboard/activity/$activity.tsx
+++ b/ui/src/routes/_dashboard/activity/$activity.tsx
@@ -1,110 +1,285 @@
-import {activities} from '@fake-data/acitivities.ts';
-import {
- BriefcaseIcon,
- CameraIcon,
- PaperAirplaneIcon,
- PencilIcon,
- TrashIcon,
-} from '@heroicons/react/24/outline';
+import {CameraIcon, PaperAirplaneIcon, TrashIcon} from '@heroicons/react/24/outline';
import {ChevronLeftIcon} from '@heroicons/react/24/solid';
-import {Link, Navigate, createFileRoute} from '@tanstack/react-router';
+import {Link, createFileRoute} from '@tanstack/react-router';
+import groupBy from 'just-group-by';
-import {Avatar} from '@/components/common';
+import {urlWithArgs} from '@/api-types';
+import {Paths} from '@/api-types/routePaths.ts';
+import {axiosInstance} from '@/axios.ts';
+import Currency from '@/components/Currency.tsx';
+import {Avatar, Button, FieldError, Form, Input, TextFormField} from '@/components/common';
+import {apiQueryOptions} from '@/hooks/useApiQuery.ts';
+import {useConfirmation} from '@/hooks/useConfirmation.tsx';
+import {queryClient} from '@/queryClient.ts';
export const Route = createFileRoute('/_dashboard/activity/$activity')({
component: RootComponent,
});
function RootComponent() {
- const {activity: activityId} = Route.useParams();
+ const {activity: activity_uid} = Route.useParams();
+ const confirm = useConfirmation();
- const activity = activities.find((e) => e.id.toString() === activityId);
+ // const {data} = useApiQuery(ApiRoutes.ACTIVITY_LIST);
+ // const currentActivity = data?.results.find((activity) => activity.uid === activity_uid);
+ // if (!currentActivity) return null;
+ // console.log(currentActivity, data);
+ const data = {
+ uid: 'activity-1',
+ urn: 'urn:activity:1',
+ user: {
+ uid: 'user-1',
+ urn: 'urn:user:1',
+ fullName: 'John Doe',
+ isActive: true,
+ },
+ group: {
+ uid: 'group-1',
+ urn: 'urn:group:1',
+ name: 'Apartment Buddies',
+ },
+ template: 'Dinner',
+ description: 'Dinner at Pizza Palace',
+ target: {
+ uid: 'target-1',
+ urn: 'urn:target:1',
+ value: 35.75,
+ },
+ createdAt: '2024-03-23T18:00:00Z',
+ };
+ const commentsData = {
+ count: 123, // Number of comments retrieved (replace with actual count)
+ next: 'https://api.example.org/accounts/?offset=400&limit=100',
+ previous: 'https://api.example.org/accounts/?offset=200&limit=100',
+ results: [
+ {
+ uid: 'comment-1',
+ urn: 'urn:comment:1',
+ user: {
+ uid: 'user-1',
+ urn: 'urn:user:1',
+ fullName: 'John Doe',
+ isActive: true,
+ },
+ content: "This looks great! Can't wait to see the final product.",
+ createdAt: '2024-03-24T10:00:00Z',
+ },
+ {
+ uid: 'comment-2',
+ urn: 'urn:comment:2',
+ user: {
+ uid: 'user-2',
+ urn: 'urn:user:2',
+ fullName: 'Jane Smith',
+ isActive: true,
+ },
+ content: 'I have a suggestion for improvement...',
+ createdAt: '2024-03-23T15:30:00Z',
+ },
+ ],
+ };
- if (!activity) {
- return
;
+ async function deleteComment(comment_uid: string) {
+ return confirm({
+ title: 'Delete comment',
+ description: (
+ <>
+ Are you sure you want to delete
Comment group?
+ This action cannot be undone.
+ >
+ ),
+ callback: async () => {
+ await axiosInstance.delete(
+ urlWithArgs(Paths.COMMENT_DETAIL, {
+ activity_uid,
+ comment_uid,
+ })
+ );
+ await queryClient.invalidateQueries(apiQueryOptions(Paths.COMMENT_LIST, {activity_uid}));
+ return null;
+ },
+ });
}
- return (
-
-
-
- Activity
-
+ function formatDate(dateString: string) {
+ const [month, year] = dateString.split('/');
-
-
-
-
-
-
-
Travel
-
-
Rs 200
-
Added by you on 31 Dec 2023
-
-
-
+ const months = [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December',
+ ];
+ const monthName = months[parseInt(month, 10) - 1];
-
-
-
-
-
-
You Paid Rs 100
-
-
Sajeel. Owes you Rs 200
-
Sajeel. Owes you Rs 400
-
+ return `${monthName} ${year}`;
+ }
+
+ function formatTime(dateString: string) {
+ const date = new Date(dateString);
+ const now = new Date();
+
+ // @ts-ignore
+ const daysDiff = Math.floor((now - date) / (1000 * 60 * 60 * 24));
+ let formattedDate;
+ if (daysDiff === 0) {
+ const hours = date.getHours().toString().padStart(2, '0');
+ const minutes = date.getMinutes().toString().padStart(2, '0');
+ formattedDate = `Today at ${hours}:${minutes} PM`;
+ } else if (daysDiff === -1) {
+ const hours = date.getHours().toString().padStart(2, '0');
+ const minutes = date.getMinutes().toString().padStart(2, '0');
+ formattedDate = `Yesterday at ${hours}:${minutes} PM`;
+ } else {
+ const options: Intl.DateTimeFormatOptions = {
+ hour: 'numeric',
+ minute: '2-digit',
+ hour12: true,
+ };
+ formattedDate = date.toLocaleDateString('en-US', options);
+ }
+ return formattedDate.split('at')[1];
+ }
+
+ return (
+
+
+
-
-
+
+
+
+ Activity
+
+
+
-
-
Dummy Comment
-
Yesterday, 22:36
-
+
{data.template}
+
+ {+data.target.value > 0 && <>{data.user.fullName} borrowed >}
+ {+data.target.value < 0 && <>You lent >}
+
+ {data.group && <> in {data.group.name}>}
+
-
-
-
-
+
+
+
+ {Object.entries(
+ groupBy(commentsData?.results ?? [], (comment) => {
+ const dateObj = new Date(comment.createdAt ?? '');
+ return dateObj.toLocaleDateString('en-US', {year: 'numeric', month: 'numeric'});
+ })
+ )
+ .sort((a, b) => (a[0] < b[0] ? -1 : +1))
+ .map(([date, comments]) => (
+
+
+
{formatDate(date)}
+
+
+ {comments.map((comment) => (
+
+
+
+
+
{comment.user.fullName}
+
{formatTime(comment.createdAt)}
+
+
{comment.content}
+
+
+
+ ))}
+
-
+
-
-
+
+
diff --git a/ui/src/routes/_dashboard/activity/-components/ActivityListItem.tsx b/ui/src/routes/_dashboard/activity/-components/ActivityListItem.tsx
index 9b84fe6..282210c 100644
--- a/ui/src/routes/_dashboard/activity/-components/ActivityListItem.tsx
+++ b/ui/src/routes/_dashboard/activity/-components/ActivityListItem.tsx
@@ -1,63 +1,70 @@
+import clx from 'clsx';
+
import {Link} from '@tanstack/react-router';
+import {Activity} from '@/api-types/components/schemas';
+import Currency from '@/components/Currency.tsx';
import {Avatar} from '@/components/common';
-interface ActivityItemProps {
- id: number;
- verb: string;
- subject: string;
- object: string;
- balance: number;
- currency: string;
-}
+export default function ActivityListItem({user, uid, template, target, createdAt}: Activity) {
+ function formatDate(dateString: string) {
+ const date = new Date(dateString);
+ const now = new Date();
+
+ // @ts-ignore
+ const daysDiff = Math.floor((now - date) / (1000 * 60 * 60 * 24));
+ let formattedDate;
+ if (daysDiff === 0) {
+ const hours = date.getHours().toString().padStart(2, '0');
+ const minutes = date.getMinutes().toString().padStart(2, '0');
+ formattedDate = `Today at ${hours}:${minutes} PM`;
+ } else if (daysDiff === -1) {
+ const hours = date.getHours().toString().padStart(2, '0');
+ const minutes = date.getMinutes().toString().padStart(2, '0');
+ formattedDate = `Yesterday at ${hours}:${minutes} PM`;
+ } else {
+ const options: Intl.DateTimeFormatOptions = {
+ day: 'numeric',
+ month: 'long',
+ hour: 'numeric',
+ minute: '2-digit',
+ hour12: true,
+ };
+ formattedDate = date.toLocaleDateString('en-US', options);
+ }
+
+ return formattedDate;
+ }
-export default function ActivityListItem({id, verb, subject, object, balance, currency}: ActivityItemProps) {
return (
-
-
- {verb === 'updated' && (
-
- "{subject}" {verb} {object}
-
- )}
- {verb === 'setting' && (
-
- "{subject}" was {object}
-
- )}
- {verb === 'paid' && (
-
- "{subject}" paid {object}
-
- )}
- {balance > 0 ? (
-
- {' '}
- You Recieved{' '}
-
- {currency} {balance}
-
-
- ) : (
-
- {' '}
- You borrowed{' '}
-
- {' '}
- {currency}
- {balance}
-
-
- )}
-
Yesterday, 22:30
+
+
+
+
{template}
+
+
+ {+(target.value ?? '0') > 0 ? 'You lent' : 'You borrowed'}
+
+
+
+
{formatDate(createdAt ?? '')}
+
);
diff --git a/ui/src/routes/_dashboard/activity/index.tsx b/ui/src/routes/_dashboard/activity/index.tsx
deleted file mode 100644
index c9cc1b3..0000000
--- a/ui/src/routes/_dashboard/activity/index.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import clsx from 'clsx';
-
-import {activities} from '@fake-data/acitivities.ts';
-import {Outlet, ScrollRestoration, createFileRoute, useMatchRoute} from '@tanstack/react-router';
-
-import ActivityListItem from './-components/ActivityListItem.tsx';
-
-export const Route = createFileRoute('/_dashboard/activity/')({
- component: ActivityLayout,
-});
-
-function ActivityLayout() {
- const matchRoute = useMatchRoute();
- const isRootLayout = matchRoute({to: '/activity'});
-
- return (
- <>
-
-
-
Activity
-
-
-
- {activities.map((e) => (
-
- ))}
-
-
-
-
-
-
-
- >
- );
-}