Skip to content

Commit

Permalink
Money Manager - Alpha Release
Browse files Browse the repository at this point in the history
  • Loading branch information
AlbanesiDev committed Aug 24, 2024
1 parent fd202d9 commit 40df4d9
Show file tree
Hide file tree
Showing 29 changed files with 531 additions and 125 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,9 @@ dist-ssr
.firebase/hosting.ZGlzdA.cache
.github/workflows/firebase-hosting-merge.yml
.github/workflows/firebase-hosting-pull-request.yml


Docs/Readme.md
src/data/helper/iconMap.ts
src/data/helper/transformDate.ts
src/data/helper/transformIcons.ts
9 changes: 9 additions & 0 deletions src/adapters/createAddaptedUser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export const createAddaptedUser = (user: any) => {
const formattedUser: any = {
id: user.id,
name: user.name,
email: user.email,
};
return formattedUser;
};
9 changes: 6 additions & 3 deletions src/config/ProvidersContainer.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ViewContextProvider,
AuthProviderModal,
MonthProvider,
SyncModalContextProvider,
} from "../presentation/contexts";
import { useTheme } from "../presentation/hooks";

Expand All @@ -30,9 +31,11 @@ const ProvidersContainer: React.FC<{ children: React.ReactNode }> = ({ children
<MonthProvider>
<ViewContextProvider>
<TransactionModalProvider>
<AuthProviderModal>
<ProfileContextProvider>{children}</ProfileContextProvider>
</AuthProviderModal>
<SyncModalContextProvider>
<AuthProviderModal>
<ProfileContextProvider>{children}</ProfileContextProvider>
</AuthProviderModal>
</SyncModalContextProvider>
</TransactionModalProvider>
</ViewContextProvider>
</MonthProvider>
Expand Down
54 changes: 34 additions & 20 deletions src/data/Infrastructure/FirestoreTransactionRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,25 @@ export class FirestoreTransactionRepository implements IFirestoreTransactionRepo
private db = getFirestore(app);
private collectionName = "users";

public async uploadTransactions(transactions: Transaction[]): Promise<void> {
const auth = getAuth(app);
const user = auth.currentUser;

if (!user) {
throw new Error("No authenticated user found.");
}

const userId = user.uid;
const userDocRef = doc(this.db, this.collectionName, userId);
const transactionsCollectionRef = collection(userDocRef, "transactions");

const batch = transactions.map(async (transaction) => {
const transactionDocRef = doc(transactionsCollectionRef, transaction.id.toString());
await setDoc(transactionDocRef, transaction);
});
await Promise.all(batch);
}

public async getTransactions(): Promise<Transaction[]> {
const auth = getAuth(app);
const user = auth.currentUser;
Expand All @@ -31,7 +50,14 @@ export class FirestoreTransactionRepository implements IFirestoreTransactionRepo
const userId = user.uid;
const q = query(collection(this.db, this.collectionName, userId, "transactions"));
const snapshot = await getDocs(q);
return snapshot.docs.map((doc) => doc.data() as Transaction);

return snapshot.docs.map((doc) => {
const data = doc.data() as Transaction;
return {
...data,
// date: timestampToDayjs(data.date as unknown as Timestamp),
};
});
}

public async addTransaction(data: Transaction): Promise<Transaction> {
Expand All @@ -48,34 +74,21 @@ export class FirestoreTransactionRepository implements IFirestoreTransactionRepo
const newTransaction = {
...data,
id: uuidv4(),
// date: dayjsToTimestamp(data.date as dayjs.Dayjs),
// category: {
// ...data.category,
// },
};

console.log(userDocRef, newTransaction);

await updateDoc(userDocRef, {
transactions: arrayUnion(newTransaction),
});

return newTransaction;
}

public async uploadTransactions(transactions: Transaction[]): Promise<void> {
const auth = getAuth(app);
const user = auth.currentUser;

if (!user) {
throw new Error("No authenticated user found.");
}

const userId = user.uid;
const userDocRef = doc(this.db, this.collectionName, userId);
const transactionsCollectionRef = collection(userDocRef, "transactions");

const batch = transactions.map(async (transaction) => {
const transactionDocRef = doc(transactionsCollectionRef, transaction.id.toString());
await setDoc(transactionDocRef, transaction);
});
await Promise.all(batch);
}

public async updateTransaction(
id: string,
updatedData: Partial<Transaction>,
Expand All @@ -92,6 +105,7 @@ export class FirestoreTransactionRepository implements IFirestoreTransactionRepo

await updateDoc(transactionDocRef, {
...updatedData,
// date: dayjsToTimestamp(updatedData.date as dayjs.Dayjs),
});

const updatedDoc = await getDoc(transactionDocRef);
Expand Down
1 change: 1 addition & 0 deletions src/data/Infrastructure/UserRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class UserRepository {}
53 changes: 53 additions & 0 deletions src/data/services/SyncTransactionsService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { User } from "firebase/auth";
import {
FirestoreTransactionRepository,
LocalStorageTransactionRepository,
} from "../Infrastructure";
import { FirestoreTransactionUseCases, LocalStorageTransactionUseCases } from "../../usecases";
import { Transaction } from "../../domain/entities";

export class SyncTransactionsService {
private storageKey: string = import.meta.env.VITE_REACT_APP_LOCALSTORAGE_KEY;
private localStorageTransactionUseCases: LocalStorageTransactionUseCases;
private firestoreTransactionUseCases: FirestoreTransactionUseCases;

constructor(private user: User | null) {
const localStorageTransactionRepository = new LocalStorageTransactionRepository();
const firestoreTransactionRepository = new FirestoreTransactionRepository();

this.localStorageTransactionUseCases = new LocalStorageTransactionUseCases(
localStorageTransactionRepository,
);
this.firestoreTransactionUseCases = new FirestoreTransactionUseCases(
firestoreTransactionRepository,
);
}

public async detectSyncTransactions(): Promise<void> {
const localStorageTransactions = await this.localStorageTransactionUseCases.getTransactions();
if (localStorageTransactions.length > 0) {
await this.wantsToSync(localStorageTransactions);
} else {
// Llamar al método getTransactions() en el componente homePage
}
}

private async wantsToSync(transactions: Transaction[]): Promise<void> {
const userConfirmed = window.confirm(
"Tienes transacciones no guardadas. ¿Deseas sincronizarlas?",
);
if (userConfirmed) {
await this.syncTransactions(transactions);
} else {
// Llamar al método getTransactions() en el componente homePage
}
}

private async syncTransactions(transactions: Transaction[]): Promise<void> {
if (this.user) {
await this.firestoreTransactionUseCases.uploadTransactions(transactions);
}

localStorage.removeItem(this.storageKey);
}
}
12 changes: 12 additions & 0 deletions src/domain/repositories/TransactionService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Category, Transaction } from "../entities/Transaction";

export interface ITransactionService {
getTransactions(): Promise<Transaction[]>;
addTransaction(data: Transaction, selectedCategory: Category): Promise<Transaction>;
updateTransaction(
id: string,
updatedData: Partial<Transaction>,
selectedCategory: Category,
): Promise<Transaction | null>;
deleteTransaction(id: string): Promise<void>;
}
9 changes: 9 additions & 0 deletions src/factories/useAuthBasedTransactionService .ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useAuth } from "reactfire";
import { AuthBasedTransactionService } from "../data/services/AuthBasedTransactionService";

const useAuthBasedTransactionService = () => {
const auth = useAuth();
return new AuthBasedTransactionService(auth.currentUser);
};

export { useAuthBasedTransactionService };
5 changes: 2 additions & 3 deletions src/presentation/components/charts/Charts.component.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { PieChart, PieChartSlotProps } from "@mui/x-charts/PieChart";
import { LineChart } from "@mui/x-charts/LineChart";
import { Col, Flex, Row } from "antd";
import { Col, Row } from "antd";
import { transformData } from "../../utils";
import { Transaction } from "../../../domain/entities";
import "./Charts.css";

interface ChartProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any;
}

const Charts: React.FC<ChartProps> = ({ data }) => {
console.log(JSON.stringify(data));
const { incomeData, expenseData } = transformData(data);

const slotProps: PieChartSlotProps = {
Expand Down
25 changes: 20 additions & 5 deletions src/presentation/components/profile-modal/profile.component.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { DownloadOutlined } from "@ant-design/icons";
import { Button, Flex, Modal } from "antd";
import { Button, Flex, Modal, Typography } from "antd";
import { signOut } from "firebase/auth";
import { useAuth } from "reactfire";
import "./Profile.css";
import { useProfile } from "../../hooks";
import { downloadCSV } from "../../utils/downloadCSV";

const { Text, Paragraph } = Typography;

const ProfileModal: React.FC = () => {
const auth = useAuth();
Expand All @@ -14,6 +17,10 @@ const ProfileModal: React.FC = () => {
closeModal();
};

const handleDownload = () => {
downloadCSV();
};

return (
<Modal
centered
Expand All @@ -24,12 +31,20 @@ const ProfileModal: React.FC = () => {
footer={null}
>
<Flex vertical gap={16}>
<h2>Perfil</h2>
<Flex vertical gap={8} className="profile_info">
<span className="profile_name">Nombre: {auth.currentUser?.displayName}</span>
<span className="profile_email">Correo electrónico: {auth.currentUser?.email}</span>
<Paragraph>
<Text strong>Nombre: </Text>
<Text type="secondary">{auth.currentUser?.displayName}</Text>
</Paragraph>

<Paragraph>
<Text strong>Correo electrónico: </Text>
<Text type="secondary">{auth.currentUser?.email}</Text>
</Paragraph>
</Flex>
<Button icon={<DownloadOutlined />}>Descargar registros</Button>
<Button block icon={<DownloadOutlined />} onClick={handleDownload}>
Descargar registros
</Button>
<Button block danger onClick={handleSignOut}>
Cerrar Sesión
</Button>
Expand Down
92 changes: 92 additions & 0 deletions src/presentation/components/sync-modal/SyncModal.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// import { Button, Flex, Modal, Result, Spin } from "antd";
// import { useState } from "react";
// import { useSyncTransactions } from "../../hooks/useSyncTransactions";
// import { useSyncModal } from "../../hooks/useSyncModal";

// type SyncState = "loading" | "success" | "error";

// const SyncModal: React.FC = () => {
// const { SyncModal, closeSyncModal } = useSyncModal();
// const [state, setState] = useState(false);

// const handleSetState = () => {
// setState(true);
// };

// return (
// <Modal open={SyncModal} onCancel={closeSyncModal} footer={null} width={430} closable={false}>
// {!state ? (
// <SyncConfirm onClose={closeSyncModal} onConfirm={handleSetState} />
// ) : (
// <Sync onClose={closeSyncModal} />
// )}
// </Modal>
// );
// };

// interface SyncConfirmProps {
// onClose: () => void;
// onConfirm: () => void;
// }

// const SyncConfirm: React.FC<SyncConfirmProps> = ({ onClose, onConfirm }) => {
// const transactionsLength = 2;

// return (
// <>
// <Result
// status="info"
// title={`Hay ${transactionsLength} ${transactionsLength > 1 ? "transacciones" : "transacción"} sin sincronizar`}
// subTitle="¿Deseas sincronizarlas?"
// />
// <Flex gap={8}>
// <Button block danger onClick={onClose}>
// Descartar
// </Button>
// <Button block type="primary" onClick={onConfirm}>
// Confirmar
// </Button>
// </Flex>
// </>
// );
// };

// const Sync: React.FC<{ onClose: () => void }> = ({ onClose }) => {
// // const [syncState, setSyncState] = useState<SyncState>("loading");
// // useSyncTransactions(syncState, setSyncState);

// return (
// <>
// {syncState === "loading" && (
// <Flex justify="center" vertical>
// <div style={{ paddingBlock: 150 }}>
// <Spin tip="Sincronizando..." size="large">
// {null}
// </Spin>
// </div>
// </Flex>
// )}
// {syncState === "success" && (
// <Result
// status="success"
// title="Transacciones sincronizadas"
// subTitle="Las transacciones han sido sincronizadas con exito"
// extra={[
// <Button block type="primary" onClick={onClose}>
// Cerrar
// </Button>,
// ]}
// />
// )}
// {syncState === "error" && (
// <Result
// status="error"
// title="Transacciones no sincronizadas"
// subTitle="Las transacciones no han sido sincronizadas"
// />
// )}
// </>
// );
// };

// export default SyncModal;
Loading

0 comments on commit 40df4d9

Please sign in to comment.