diff --git a/backend/backend/urls.py b/backend/backend/urls.py index 083651d..f7846b7 100644 --- a/backend/backend/urls.py +++ b/backend/backend/urls.py @@ -16,17 +16,49 @@ Including another URLconf from django.conf import settings from django.conf.urls.static import static from django.contrib import admin -from django.urls import path +from drf_yasg.views import get_schema_view +from drf_yasg import openapi +from rest_framework import permissions +from django.urls import include, path, re_path from foldbank.api import AccountsAPI, TransactionsAPI, UpcomingRecurringPaymentAPI +schema_view = get_schema_view( + openapi.Info( + title="Grapevine API", + default_version="v1", + description="", + terms_of_service="https://gvine.app/terms/", + contact=openapi.Contact(email="support@gvine.app"), + license=openapi.License(name="BSD License"), + ), + public=True, + permission_classes=[permissions.AllowAny], +) + urlpatterns = [ path("admin/", admin.site.urls), path("api/v1/transactions/", TransactionsAPI.as_view()), path("api/v1/accounts/", AccountsAPI.as_view()), path("api/v1/recurringPayments/upcoming/", UpcomingRecurringPaymentAPI.as_view()), + re_path( + r"^swagger(?P\.json|\.yaml)$", + schema_view.without_ui(cache_timeout=0), + name="schema-json", + ), + re_path( + r"^swagger/$", + schema_view.with_ui("swagger", cache_timeout=0), + name="schema-swagger-ui", + ), + re_path( + r"^redoc/$", + schema_view.with_ui("redoc", cache_timeout=0), + name="schema-redoc", + ), ] + if settings.DEBUG: urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/backend/foldbank/admin.py b/backend/foldbank/admin.py index c5cf9f5..0dd9ce9 100644 --- a/backend/foldbank/admin.py +++ b/backend/foldbank/admin.py @@ -84,6 +84,12 @@ class UserAdminConfig(UserAdmin): ) +class TransactionConfig(admin.ModelAdmin): + list_display = ( + "from_account", + "created_at", + ) + # Register your models here. admin.site.register(User, UserAdminConfig) @@ -91,6 +97,6 @@ admin.site.register(Tag) admin.site.register(Bank) admin.site.register(Account) admin.site.register(RecurringPayment) -admin.site.register(Transaction) +admin.site.register(Transaction, TransactionConfig) admin.site.register(RecurringPaymentTransactionLink) diff --git a/backend/foldbank/api.py b/backend/foldbank/api.py index 0851d14..520e420 100644 --- a/backend/foldbank/api.py +++ b/backend/foldbank/api.py @@ -88,7 +88,7 @@ class LimitOffsetPagination(_LimitOffsetPagination): def get_transactions(user): - return Transaction.objects.filter(from_account__user=user).order_by("created_at") + return Transaction.objects.filter(from_account__user=user).order_by("-created_at") class TransactionsAPI(generics.ListAPIView): diff --git a/backend/foldbank/management/commands/populatedb.py b/backend/foldbank/management/commands/populatedb.py index 326e23d..5a3d3a2 100644 --- a/backend/foldbank/management/commands/populatedb.py +++ b/backend/foldbank/management/commands/populatedb.py @@ -1,5 +1,6 @@ -# standard imports +# standard importspopulatedb import datetime +from zoneinfo import ZoneInfo # third-party imports from django.utils.timezone import make_aware @@ -226,6 +227,9 @@ class Command(BaseCommand): Account.objects.get_or_create(**account) # recurring payment + ist = ZoneInfo("Asia/Kolkata") + today = datetime.datetime.now(tz=ist) + yesterday = datetime.datetime.now(tz=ist) - datetime.timedelta(days=1) recurring_payments = [ { "from_account": Account.objects.get(user=nishant, bank=Bank.objects.get(name="Axis Bank")), @@ -233,9 +237,9 @@ class Command(BaseCommand): "amount": "36031", "tag": Tag.objects.get(title="Rent"), "defaults": { - "created_at": make_aware(datetime.datetime(year=2023, month=1, day=23)), + "created_at": datetime.datetime(year=2023, month=1, day=23, tzinfo=ist), "frequency": datetime.timedelta(days=30), - "due_on": make_aware(datetime.datetime(year=2023, month=1, day=23)), + "due_on": datetime.datetime(year=2023, month=1, day=23, tzinfo=ist), }, }, { @@ -244,9 +248,9 @@ class Command(BaseCommand): "amount": "36031", "tag": Tag.objects.get(title="Rent"), "defaults": { - "created_at": make_aware(datetime.datetime(year=2023, month=1, day=20)), + "created_at": datetime.datetime(year=2023, month=1, day=20, tzinfo=ist), "frequency": datetime.timedelta(days=30), - "due_on": make_aware(datetime.datetime(year=2023, month=2, day=19)), + "due_on": datetime.datetime(year=2023, month=2, day=19, tzinfo=ist), } }, ] @@ -261,7 +265,7 @@ class Command(BaseCommand): "amount": "3940", "tag": Tag.objects.get(title="Tag"), "defaults": { - "created_at": make_aware(datetime.datetime(year=2023, month=1, day=23, hour=11, minute=17)), + "created_at": datetime.datetime(year=today.year, month=today.month, day=today.day, hour=11, minute=17, tzinfo=ist), } }, { @@ -270,7 +274,7 @@ class Command(BaseCommand): "amount": "35", "tag": Tag.objects.get(title="Food & Drinks", sub_category="Drink"), "defaults": { - "created_at": make_aware(datetime.datetime(year=2023, month=1, day=23, hour=23, minute=45)), + "created_at": datetime.datetime(year=today.year, month=today.month, day=today.day, hour=23, minute=45, tzinfo=ist), } }, { @@ -279,7 +283,7 @@ class Command(BaseCommand): "amount": "2399", "tag": Tag.objects.get(title="Groceries"), "defaults": { - "created_at": make_aware(datetime.datetime(year=2023, month=1, day=23, hour=5, minute=37)), + "created_at": datetime.datetime(year=yesterday.year, month=yesterday.month, day=yesterday.day, hour=5, minute=37, tzinfo=ist), } }, { @@ -288,7 +292,7 @@ class Command(BaseCommand): "amount": "312", "tag": Tag.objects.get(title="Food & Drinks", sub_category="Food"), "defaults": { - "created_at": make_aware(datetime.datetime(year=2023, month=1, day=23, hour=12, minute=17)), + "created_at": datetime.datetime(year=yesterday.year, month=yesterday.month, day=yesterday.day, hour=12, minute=17, tzinfo=ist), } }, { @@ -297,7 +301,7 @@ class Command(BaseCommand): "amount": "75", "tag": Tag.objects.get(title="Transport"), "defaults": { - "created_at": make_aware(datetime.datetime(year=2023, month=1, day=2, hour=10, minute=32)), + "created_at": datetime.datetime(year=2023, month=1, day=2, hour=10, minute=32, tzinfo=ist), } }, { @@ -306,7 +310,7 @@ class Command(BaseCommand): "amount": "249", "tag": Tag.objects.get(title="Food & Drinks", sub_category="Swiggy"), "defaults": { - "created_at": make_aware(datetime.datetime(year=2023, month=1, day=1, hour=11, minute=17)), + "created_at": datetime.datetime(year=2023, month=1, day=1, hour=11, minute=17, tzinfo=ist), } }, { @@ -315,11 +319,13 @@ class Command(BaseCommand): "amount": "199", "tag": Tag.objects.get(title="Subscription", sub_category="Netflix"), "defaults": { - "created_at": make_aware(datetime.datetime(year=2023, month=1, day=23, hour=23, minute=45)), + "created_at": datetime.datetime(year=2022, month=12, day=31, hour=23, minute=45, tzinfo=ist), } }, ] # Transaction.objects.all().delete() for t in transactions: - Transaction.objects.get_or_create(**t) + tnx, _ = Transaction.objects.get_or_create(**t) + tnx.created_at = t["defaults"]["created_at"] + tnx.save() diff --git a/dev.env b/dev.env index eaefd4f..568fdc6 100644 --- a/dev.env +++ b/dev.env @@ -1,8 +1,9 @@ # Django SECRET_KEY=sample DEBUG=true -ALLOWED_HOSTS=0.0.0.0,localhost,127.0.0.1,* -CSRF_TRUSTED_ORIGINS=https://foldbank.codingcoffee.me +ALLOWED_HOSTS=0.0.0.0,localhost,127.0.0.1,localhost:3000 +CSRF_TRUSTED_ORIGINS=http://localhost:3000,https://foldbank.codingcoffee.me +CORS_ALLOWED_ORIGINS=http://localhost:3000,https://foldbank.codingcoffee.me DJANGO_LOG_LEVEL=INFO # Django - Postgres POSTGRES_SERVER=postgres-db diff --git a/docker-compose.yml b/docker-compose.yml index bace45a..dac7a90 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,15 +3,15 @@ version: '3.7' services: - # web: - # image: node:16.19.1-alpine3.17 - # restart: unless-stopped - # volumes: - # - ./web:/app - # working_dir: /app - # entrypoint: npm run dev - # ports: - # - 3000:3000 + web: + image: node:16.19.1-alpine3.17 + restart: unless-stopped + volumes: + - ./web:/app + working_dir: /app + entrypoint: npm run dev + ports: + - 3000:3000 backend: image: foldbank-backend-dev diff --git a/web/app/components/RecentTransactions.tsx b/web/app/components/RecentTransactions.tsx index ed0765d..f2c3f9f 100644 --- a/web/app/components/RecentTransactions.tsx +++ b/web/app/components/RecentTransactions.tsx @@ -1,64 +1,109 @@ +"use client" import React from 'react' import styles from './recentTnx.module.css' import { BsTriangleFill } from 'react-icons/bs'; import SingleTransaction from './SingleTransaction' +import useSWR from 'swr' + export default function RecentTransactions() { - const transactions = [ - { - title: "Fenny's Banglore", - datetime: "Today, 11:17 am", - amount: "3,940", - tag: "Tag", - }, { - title: "Sendoor", - datetime: "Today, 11:45 pm", - amount: "35", - tag: "Food & Drinks", - icon_type: "drink", - }, { - title: "Reliance Fresh", - datetime: "Yesterday, 5:37 pm", - amount: "2,399", - tag: "Groceries", - icon_type: "groceries", - }, { - title: "Chai Point", - datetime: "Yesterday, 12:17 pm", - amount: "312", - tag: "Food & Drinks", - icon_type: "food", - }, { - title: "Uber", - datetime: "Jan 2, 10:32 am", - amount: "75", - tag: "Transport", - icon_type: "transport", - }, { - title: "Swiggy", - datetime: "Jan 1, 11:17 pm", - amount: "249", - tag: "Food & Drinks", - icon_type: "swiggy", - }, { - title: "Netflix", - datetime: "Dec 31, 11:59 pm", - amount: "199", - tag: "Subsciption", - icon_type: "netflix", + const fetcher = (...args) => fetch(...args).then((res) => res.json()) + const { data, error } = useSWR(`http://localhost:8000/api/v1/transactions/`, fetcher) + + if (error) return
Error: Failed to load
+ if (!data) return
Loading...
+ + const transactions = data.results + + // const transactions = [ + // { + // title: "Fenny's Banglore", + // datetime: "Today, 11:17 am", + // amount: "3,940", + // tag: "Tag", + // }, { + // title: "Sendoor", + // datetime: "Today, 11:45 pm", + // amount: "35", + // tag: "Food & Drinks", + // icon_type: "drink", + // }, { + // title: "Reliance Fresh", + // datetime: "Yesterday, 5:37 pm", + // amount: "2,399", + // tag: "Groceries", + // icon_type: "groceries", + // }, { + // title: "Chai Point", + // datetime: "Yesterday, 12:17 pm", + // amount: "312", + // tag: "Food & Drinks", + // icon_type: "food", + // }, { + // title: "Uber", + // datetime: "Jan 2, 10:32 am", + // amount: "75", + // tag: "Transport", + // icon_type: "transport", + // }, { + // title: "Swiggy", + // datetime: "Jan 1, 11:17 pm", + // amount: "249", + // tag: "Food & Drinks", + // icon_type: "swiggy", + // }, { + // title: "Netflix", + // datetime: "Dec 31, 11:59 pm", + // amount: "199", + // tag: "Subsciption", + // icon_type: "netflix", + // } + // ] + + const relateive_date = (date) => { + const today = new Date(); + let fuzzy; + + if (today.getYear() == date.getYear()) { + if (today.getMonth() == date.getMonth()) { + if (today.getDate() == date.getDate()) { + fuzzy = 'Today'; + } else if (today.getDate() == date.getDate() + 1) { + fuzzy = 'Yesterday'; + } + } } - ] + + if (fuzzy == undefined){ + const monthNames = [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + ]; + fuzzy = `${monthNames[date.getMonth()]} ${date.getDate()}` + } + + let hours = date.getHours() + let ampm = 'AM'; + if (hours >= 12) { + ampm = 'PM'; + hours -= 12; + } + + fuzzy = fuzzy + `, ${hours}:${date.getMinutes()} ${ampm}` + return fuzzy + } + const recent_transactions = []; transactions.forEach((transaction) => { recent_transactions.push( ) }) diff --git a/web/package-lock.json b/web/package-lock.json index 0388890..332149d 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -23,6 +23,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-icons": "^4.7.1", + "swr": "^2.1.0", "typescript": "4.9.5" } }, @@ -3901,6 +3902,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.1.0.tgz", + "integrity": "sha512-4hYl5p3/KalQ1MORealM79g/DtLohmud6yyfXw5l4wjtFksYUnocRFudvyaoUtgj3FrVNK9lS25Av9dsZYvz0g==", + "dependencies": { + "use-sync-external-store": "^1.2.0" + }, + "engines": { + "pnpm": "7" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/synckit": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", @@ -4061,6 +4076,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6776,6 +6799,14 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "swr": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.1.0.tgz", + "integrity": "sha512-4hYl5p3/KalQ1MORealM79g/DtLohmud6yyfXw5l4wjtFksYUnocRFudvyaoUtgj3FrVNK9lS25Av9dsZYvz0g==", + "requires": { + "use-sync-external-store": "^1.2.0" + } + }, "synckit": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", @@ -6895,6 +6926,12 @@ "punycode": "^2.1.0" } }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/web/package.json b/web/package.json index 6f573d3..578372f 100644 --- a/web/package.json +++ b/web/package.json @@ -24,6 +24,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-icons": "^4.7.1", + "swr": "^2.1.0", "typescript": "4.9.5" } }