Skip to content

Commit

Permalink
refactor: implement post message in expense domain
Browse files Browse the repository at this point in the history
  • Loading branch information
mnindrazaka committed Sep 17, 2024
1 parent 3f849a9 commit db72c21
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 194 deletions.
4 changes: 3 additions & 1 deletion libs/ui/src/base/utils/postMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ export type PostMessageEvent =
| { type: 'MaterialDeleteConfirmation'; materialId: number }
| { type: 'MaterialDeleteSuccess' }
| { type: 'ProductDeleteConfirmation'; productId: number }
| { type: 'ProductDeleteSuccess' };
| { type: 'ProductDeleteSuccess' }
| { type: 'ExpenseDeleteConfirmation'; expenseId: number }
| { type: 'ExpenseDeleteSuccess' };

export const usePostMessage = (
onReceiveMessage?: (event: PostMessageEvent) => void
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// eslint-disable-next-line @nx/enforce-module-boundaries
import { Category, useCategoryList } from '../../../../../api-contract/src';
import { useCallback } from 'react';
import { PostMessageEvent, usePostMessage } from '../../../base';
import { useRouter } from 'solito/router';

Expand All @@ -8,11 +9,14 @@ export const useCategoryListState = () => {

const { data, status, error, refetch } = useCategoryList();

const onReceiveMessage = (event: PostMessageEvent) => {
if (event.type === 'CategoryDeleteSuccess') {
refetch();
}
};
const onReceiveMessage = useCallback(
(event: PostMessageEvent) => {
if (event.type === 'CategoryDeleteSuccess') {
refetch();
}
},
[refetch]
);

const { postMessage } = usePostMessage(onReceiveMessage);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
// eslint-disable-next-line @nx/enforce-module-boundaries
import {
useExpenseDeleteById,
useExpenseFindById,
} from '../../../../../api-contract/src';
import { useExpenseDeleteById } from '../../../../../api-contract/src';
import { useCallback, useState } from 'react';
import { useToastController } from '@tamagui/toast';
import { PostMessageEvent, usePostMessage } from '../../../base';

export type UseExpenseDeleteAlertStateProps = {
expenseId: number;
onSuccess: () => void;
};
export const useExpenseDeleteAlertState = () => {
const [expenseId, setExpenseId] = useState<number>();
const { status, mutateAsync } = useExpenseDeleteById(expenseId ?? NaN);

const onReceiveMessage = useCallback((event: PostMessageEvent) => {
if (event.type === 'ExpenseDeleteConfirmation') {
setExpenseId(event.expenseId);
}
}, []);

export const useExpenseDeleteAlertState = ({
expenseId,
onSuccess,
}: UseExpenseDeleteAlertStateProps) => {
const { status, mutateAsync } = useExpenseDeleteById(expenseId);
const { data } = useExpenseFindById(expenseId);
const { postMessage } = usePostMessage(onReceiveMessage);

const toast = useToastController();

const onButtonConfirmPress = () => {
mutateAsync({})
.then(() => toast.show('Expense deleted successfully'))
.then(onSuccess)
.then(() => {
toast.show('Expense deleted successfully');
postMessage({ type: 'ExpenseDeleteSuccess' });
setExpenseId(undefined);
})
.catch(() => toast.show('Failed to delete expense'));
};

return { status, onButtonConfirmPress };
const isOpen = typeof expenseId === 'number';

const onCancel = () => setExpenseId(undefined);

return { status, onButtonConfirmPress, isOpen, onCancel };
};
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
import { AlertDialog, Button, XStack, YStack } from 'tamagui';
import { useExpenseDeleteAlertState } from './ExpenseDeleteAlert.state';

export type ExpenseDeleteAlertProps = {
expenseId: number;
onSuccess: () => void;
onCancel: () => void;
};

export const ExpenseDeleteAlert = ({
expenseId,
onSuccess,
onCancel,
}: ExpenseDeleteAlertProps) => {
const { onButtonConfirmPress, status } = useExpenseDeleteAlertState({
expenseId,
onSuccess,
});
export const ExpenseDeleteAlert = () => {
const { onButtonConfirmPress, status, onCancel, isOpen } =
useExpenseDeleteAlertState();
return (
<AlertDialog open onOpenChange={onCancel} modal>
<AlertDialog open={isOpen} onOpenChange={onCancel} modal>
<AlertDialog.Portal>
<AlertDialog.Overlay
key="overlay"
Expand Down Expand Up @@ -55,15 +43,13 @@ export const ExpenseDeleteAlert = ({
<AlertDialog.Cancel asChild>
<Button disabled={status === 'pending'}>No</Button>
</AlertDialog.Cancel>
<AlertDialog.Action asChild>
<Button
theme="active"
onPress={onButtonConfirmPress}
disabled={status === 'pending'}
>
Yes
</Button>
</AlertDialog.Action>
<Button
theme="active"
onPress={onButtonConfirmPress}
disabled={status === 'pending'}
>
Yes
</Button>
</XStack>
</YStack>
</AlertDialog.Content>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,45 @@
import { useRouter } from 'solito/router';
// eslint-disable-next-line @nx/enforce-module-boundaries
import { useExpenseList } from '../../../../../api-contract/src';
import { Expense, useExpenseList } from '../../../../../api-contract/src';
import { PostMessageEvent, usePostMessage } from '../../../base';
import { useCallback } from 'react';

export const useExpenseListState = () => {
const router = useRouter();

const { data, status, error, refetch } = useExpenseList({
sortBy: 'created_at',
order: 'desc',
});

const onReceiveMessage = useCallback(
(event: PostMessageEvent) => {
if (event.type === 'ExpenseDeleteSuccess') {
refetch();
}
},
[refetch]
);

const { postMessage } = usePostMessage(onReceiveMessage);

const onDeleteMenuPress = (expense: Expense) => {
postMessage({
type: 'ExpenseDeleteConfirmation',
expenseId: expense.id,
});
};

const onEditMenuPress = (expense: Expense) => {
router.push(`/expenses/${expense.id}`);
};

return {
expenses: data?.data ?? [],
status,
error,
refetch,
onDeleteMenuPress,
onEditMenuPress,
};
};
58 changes: 13 additions & 45 deletions libs/ui/src/expenses/components/ExpenseList/ExpenseList.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,12 @@
import {
EmptyView,
ErrorView,
ListItem,
ListItemMenu,
LoadingView,
} from '../../../base';
import { EmptyView, ErrorView, LoadingView } from '../../../base';
import { YStack } from 'tamagui';
import { useExpenseListState } from './ExpenseList.state';
// eslint-disable-next-line @nx/enforce-module-boundaries
import { Expense } from '../../../../../api-contract/src';
import dayjs from 'dayjs';
import { Calendar, Clock, Wallet } from '@tamagui/lucide-icons';
import { FlatList } from 'react-native';
import { ExpenseListItem } from '../ExpenseListItem';

export type ExpenseListProps = {
itemMenus: (Omit<ListItemMenu, 'onPress' | 'isShown'> & {
onPress: (expense: Expense) => void;
isShown?: (expense: Expense) => void;
})[];
onItemPress: (expense: Expense) => void;
};

export const ExpenseList = ({ itemMenus, onItemPress }: ExpenseListProps) => {
const { expenses, refetch, status } = useExpenseListState();
export const ExpenseList = () => {
const { expenses, refetch, status, onDeleteMenuPress, onEditMenuPress } =
useExpenseListState();
return (
<YStack gap="$3" flex={1}>
{status === 'pending' ? (
Expand All @@ -33,30 +17,14 @@ export const ExpenseList = ({ itemMenus, onItemPress }: ExpenseListProps) => {
nestedScrollEnabled
data={expenses}
renderItem={({ item: expense }) => (
<ListItem
title={expense.budget.name}
subtitle={`Rp. ${expense.total.toLocaleString('id')}`}
onPress={() => onItemPress(expense)}
menus={itemMenus.map((itemMenu) => ({
...itemMenu,
onPress: () => itemMenu.onPress(expense),
isShown: () =>
itemMenu.isShown ? itemMenu.isShown(expense) : true,
}))}
footerItems={[
{
icon: Calendar,
value: dayjs(expense.createdAt).format('DD/MM/YYYY'),
},
{
icon: Clock,
value: dayjs(expense.createdAt).format('HH:mm'),
},
{
icon: Wallet,
value: expense.wallet.name,
},
]}
<ExpenseListItem
budgetName={expense.budget.name}
createdAt={expense.createdAt}
walletName={expense.wallet.name}
total={expense.total}
onDeleteMenuPress={() => onDeleteMenuPress(expense)}
onEditMenuPress={() => onEditMenuPress(expense)}
onPress={() => onEditMenuPress(expense)}
/>
)}
keyExtractor={(item) => item.id.toString()}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Calendar, Clock, Pencil, Trash, Wallet } from '@tamagui/lucide-icons';
import { ListItem } from '../../../base';
import dayjs from 'dayjs';
import { XStackProps } from 'tamagui';

export type ExpenseListItemProps = {
budgetName: string;
total: number;
createdAt: string;
walletName: string;
onEditMenuPress?: () => void;
onDeleteMenuPress?: () => void;
} & XStackProps;

export const ExpenseListItem = ({
budgetName,
createdAt,
total,
walletName,
onEditMenuPress,
onDeleteMenuPress,
...xStackProps
}: ExpenseListItemProps) => {
return (
<ListItem
title={budgetName}
subtitle={`Rp. ${total.toLocaleString('id')}`}
menus={[
{
title: 'Edit',
icon: Pencil,
onPress: onEditMenuPress,
isShown: typeof onEditMenuPress === 'function',
},
{
title: 'Delete',
icon: Trash,
onPress: onDeleteMenuPress,
isShown: typeof onDeleteMenuPress === 'function',
},
]}
footerItems={[
{
icon: Calendar,
value: dayjs(createdAt).format('DD/MM/YYYY'),
},
{
icon: Clock,
value: dayjs(createdAt).format('HH:mm'),
},
{
icon: Wallet,
value: walletName,
},
]}
{...xStackProps}
/>
);
};
1 change: 1 addition & 0 deletions libs/ui/src/expenses/components/ExpenseListItem/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ExpenseListItem'

This file was deleted.

Loading

0 comments on commit db72c21

Please sign in to comment.