diff --git a/apps/dashboard/src/app/[locale]/@dashboard/(root)/vault/[[...folders]]/page.tsx b/apps/dashboard/src/app/[locale]/@dashboard/(root)/vault/[[...folders]]/page.tsx index e82facf51b..baadf33d90 100644 --- a/apps/dashboard/src/app/[locale]/@dashboard/(root)/vault/[[...folders]]/page.tsx +++ b/apps/dashboard/src/app/[locale]/@dashboard/(root)/vault/[[...folders]]/page.tsx @@ -1,3 +1,4 @@ +import { Breadcrumbs } from "@/components/breadcrumbs"; import { Table } from "@/components/tables/vault"; import { Metadata } from "next"; @@ -6,5 +7,10 @@ export const metadata: Metadata = { }; export default function Vault({ params }) { - return ; + return ( +
+ +
+ + ); } diff --git a/apps/dashboard/src/components/breadcrumbs.tsx b/apps/dashboard/src/components/breadcrumbs.tsx new file mode 100644 index 0000000000..f98d792af1 --- /dev/null +++ b/apps/dashboard/src/components/breadcrumbs.tsx @@ -0,0 +1,3 @@ +export function Breadcrumbs({ folders }) { + return folders?.map((folder) => {folder}); +} diff --git a/apps/dashboard/src/components/file-icon.tsx b/apps/dashboard/src/components/file-icon.tsx new file mode 100644 index 0000000000..8cca63392c --- /dev/null +++ b/apps/dashboard/src/components/file-icon.tsx @@ -0,0 +1,28 @@ +import { Icons } from "@midday/ui/icons"; + +export function FileIcon({ mimetype, name }) { + if (name === "exports") { + return ; + } + + if (name === "inbox") { + return ; + } + + if (name === "transactions") { + return ; + } + + if (mimetype?.startsWith("image")) { + return ; + } + + switch (mimetype) { + case "application/pdf": + return ; + case "application/zip": + return ; + default: + return ; + } +} diff --git a/apps/dashboard/src/components/tables/vault/data-table-row.tsx b/apps/dashboard/src/components/tables/vault/data-table-row.tsx index 3f2851c158..72a4e33e34 100644 --- a/apps/dashboard/src/components/tables/vault/data-table-row.tsx +++ b/apps/dashboard/src/components/tables/vault/data-table-row.tsx @@ -1,18 +1,37 @@ "use client"; +import { FileIcon } from "@/components/file-icon"; +import { formatSize } from "@/utils/format"; +import { Icons } from "@midday/ui/icons"; import { TableCell, TableRow } from "@midday/ui/table"; +import { format } from "date-fns"; import { usePathname, useRouter } from "next/navigation"; export function DataTableRow({ data }) { const router = useRouter(); const pathname = usePathname(); + const handleNavigate = () => router.push(`${pathname}/${data.name}`); return ( - {data.name} - {data.created_at} - wef + +
+ + {data.name} + {data?.metadata?.size && ( + + {formatSize(data.metadata.size)} + + )} +
+
+ + {data?.created_at && format(new Date(data.created_at), "MMM d, yyyy")} + + + +
); } diff --git a/apps/dashboard/src/components/tables/vault/data-table.tsx b/apps/dashboard/src/components/tables/vault/data-table.tsx index 3e98fbac1f..f67f0f6589 100644 --- a/apps/dashboard/src/components/tables/vault/data-table.tsx +++ b/apps/dashboard/src/components/tables/vault/data-table.tsx @@ -13,8 +13,8 @@ export function DataTable({ data }) { Name - Uploaded - Actions + Uploaded + Actions diff --git a/packages/jobs/src/transactions/notification.ts b/packages/jobs/src/transactions/notification.ts index 79f567cca3..75e52597a8 100644 --- a/packages/jobs/src/transactions/notification.ts +++ b/packages/jobs/src/transactions/notification.ts @@ -3,7 +3,6 @@ import { getI18n } from "@midday/email/locales"; import { TriggerEvents, triggerBulk } from "@midday/notification"; import { renderAsync } from "@react-email/components"; import { eventTrigger } from "@trigger.dev/sdk"; -import { revalidateTag } from "next/cache"; import { z } from "zod"; import { client, supabase } from "../client"; import { Events, Jobs } from "../constants"; @@ -36,83 +35,81 @@ client.defineJob({ .select("team_id, user:user_id(id, full_name, avatar_url, email, locale)") .eq("team_id", teamId); - if (transactions?.length && transactions.length > 0) { - revalidateTag(`transactions_${teamId}`); - revalidateTag(`spending_${teamId}`); - revalidateTag(`metrics_${teamId}`); + const notificationEvents = await Promise.all( + usersData?.map(async ({ user, team_id }) => { + const { t } = getI18n({ locale: user.locale }); - const notificationEvents = await Promise.all( - usersData?.map(async ({ user, team_id }) => { - const { t } = getI18n({ locale: user.locale }); + return transactions.map((transaction) => ({ + name: TriggerEvents.TransactionNewInApp, + payload: { + transactionId: transaction.id, + description: t( + { id: "notifications.transaction" }, + { + amount: Intl.NumberFormat(user.locale, { + style: "currency", + currency: transaction.currency, + }).format(transaction.amount), + from: transaction.name, + } + ), + }, + user: { + subscriberId: user.id, + teamId: team_id, + email: user.email, + fullName: user.full_name, + avatarUrl: user.avatar_url, + }, + })); + }) + ); - return transactions.map((transaction) => ({ - name: TriggerEvents.TransactionNewInApp, - payload: { - transactionId: transaction.id, - description: t( - { id: "notifications.transaction" }, - { - amount: Intl.NumberFormat(user.locale, { - style: "currency", - currency: transaction.currency, - }).format(transaction.amount), - from: transaction.name, - } - ), - }, - user: { - subscriberId: user.id, - teamId: team_id, - email: user.email, - fullName: user.full_name, - avatarUrl: user.avatar_url, - }, - })); - }) + if (notificationEvents?.length) { + await io.logger.log( + `Sending notifications: ${notificationEvents.length}` ); + triggerBulk(notificationEvents.flat()); + } - if (notificationEvents?.length) { - triggerBulk(notificationEvents.flat()); - } - - const emailEvents = await Promise.all( - usersData?.map(async ({ user, team_id }) => { - const { t } = getI18n({ locale: user.locale }); + const emailEvents = await Promise.all( + usersData?.map(async ({ user, team_id }) => { + const { t } = getI18n({ locale: user.locale }); - const html = await renderAsync( - TransactionsEmail({ - fullName: user.full_name, - transactions: transactions.map((transaction) => ({ - id: transaction.id, - date: transaction.date, - amount: transaction.amount, - name: transaction.name, - currency: transaction.currency, - })), - locale: user.locale, - }) - ); + const html = await renderAsync( + TransactionsEmail({ + fullName: user.full_name, + transactions: transactions.map((transaction) => ({ + id: transaction.id, + date: transaction.date, + amount: transaction.amount, + name: transaction.name, + currency: transaction.currency, + })), + locale: user.locale, + }) + ); - return { - name: TriggerEvents.TransactionNewEmail, - payload: { - subject: t({ id: "transactions.subject" }), - html, - }, - user: { - subscriberId: user.id, - teamId: team_id, - email: user.email, - fullName: user.full_name, - avatarUrl: user.avatar_url, - }, - }; - }) - ); + return { + name: TriggerEvents.TransactionNewEmail, + payload: { + subject: t({ id: "transactions.subject" }), + html, + }, + user: { + subscriberId: user.id, + teamId: team_id, + email: user.email, + fullName: user.full_name, + avatarUrl: user.avatar_url, + }, + }; + }) + ); - if (emailEvents?.length) { - triggerBulk(emailEvents); - } + if (emailEvents?.length) { + await io.logger.log(`Sending emails: ${emailEvents.length}`); + triggerBulk(emailEvents); } }, }); diff --git a/packages/jobs/src/transactions/sync.ts b/packages/jobs/src/transactions/sync.ts index 454a94e9eb..e785627701 100644 --- a/packages/jobs/src/transactions/sync.ts +++ b/packages/jobs/src/transactions/sync.ts @@ -18,6 +18,8 @@ client.defineJob({ .eq("id", ctx.source.id) .single(); + const teamId = data?.team_id; + // Update bank account last_accessed await io.supabase.client .from("bank_accounts") @@ -26,8 +28,8 @@ client.defineJob({ }) .eq("id", ctx.source.id); - revalidateTag(`bank_accounts_${data?.team_id}`); - await io.logger.info(`bank_accounts_${data?.team_id}`); + revalidateTag(`bank_accounts_${teamId}`); + await io.logger.info(`bank_accounts_${teamId}`); if (!data) { await io.logger.error(`Bank account not found: ${ctx.source.id}`); @@ -42,7 +44,7 @@ client.defineJob({ .upsert( transformTransactions(transactions?.booked, { accountId: data?.id, - teamId: data?.team_id, + teamId, }), { onConflict: "internal_id", @@ -51,20 +53,28 @@ client.defineJob({ ) .select(); - await io.sendEvent("🔔 Send notifications", { - name: Events.TRANSACTIONS_NOTIFICATION, - payload: { - teamId: data?.team_id, - transactions: transactionsData, - }, - }); + if (transactionsData && transactionsData.length > 0) { + await io.logger.log(`Sending notifications: ${transactionsData.length}`); + + revalidateTag(`transactions_${teamId}`); + revalidateTag(`spending_${teamId}`); + revalidateTag(`metrics_${teamId}`); - await io.sendEvent("💅 Enrich Transactions", { - name: Events.TRANSACTIONS_ENCRICHMENT, - payload: { - teamId: data?.team_id, - }, - }); + await io.sendEvent("🔔 Send notifications", { + name: Events.TRANSACTIONS_NOTIFICATION, + payload: { + teamId, + transactions: transactionsData, + }, + }); + + await io.sendEvent("💅 Enrich Transactions", { + name: Events.TRANSACTIONS_ENCRICHMENT, + payload: { + teamId, + }, + }); + } if (error) { await io.logger.error(JSON.stringify(error, null, 2)); diff --git a/packages/ui/src/components/icons.tsx b/packages/ui/src/components/icons.tsx index a386b63af7..1d21a018ef 100644 --- a/packages/ui/src/components/icons.tsx +++ b/packages/ui/src/components/icons.tsx @@ -3,16 +3,22 @@ import { Settings } from "lucide-react"; import { MdApartment, MdBarChart, + MdBrokenImage, MdCelebration, + MdDescription, MdDesk, MdDevices, + MdDriveFileMove, MdDynamicForm, MdExpandMore, MdFastfood, MdFence, MdFlightTakeoff, + MdFolderOpen, + MdFolderZip, MdHomeWork, MdInventory2, + MdMoreHoriz, MdOutlineAccountBalanceWallet, MdOutlineCategory, MdOutlineDescription, @@ -25,9 +31,11 @@ import { MdPayments, MdPeople, MdPerson, + MdPictureAsPdf, MdRefresh, MdSave, MdSensors, + MdTopic, MdTrendingDown, MdTrendingUp, } from "react-icons/md"; @@ -316,4 +324,12 @@ export const Icons = { Apartment: MdApartment, Sensors: MdSensors, DynamicForm: MdDynamicForm, + MoreHoriz: MdMoreHoriz, + Pdf: MdPictureAsPdf, + DriveFileMove: MdDriveFileMove, + FolderOpen: MdFolderOpen, + Topic: MdTopic, + BrokenImage: MdBrokenImage, + Description: MdDescription, + FolderZip: MdFolderZip, };