This commit is contained in:
Flatlogic Bot 2026-02-01 13:51:45 +00:00
parent 27bd5182e3
commit f6917d7012
23 changed files with 511 additions and 201 deletions

Binary file not shown.

View File

@ -1,3 +1,15 @@
from django.contrib import admin
from .models import Category, Transaction
# Register your models here.
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'type', 'icon')
list_filter = ('type',)
search_fields = ('name',)
@admin.register(Transaction)
class TransactionAdmin(admin.ModelAdmin):
list_display = ('date', 'type', 'amount', 'category', 'description')
list_filter = ('type', 'category', 'date')
search_fields = ('description',)
date_hierarchy = 'date'

18
core/forms.py Normal file
View File

@ -0,0 +1,18 @@
from django import forms
from .models import Transaction, Category
class TransactionForm(forms.ModelForm):
class Meta:
model = Transaction
fields = ['amount', 'type', 'category', 'date', 'description']
widgets = {
'date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'amount': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'placeholder': '0.00'}),
'type': forms.Select(attrs={'class': 'form-select'}),
'category': forms.Select(attrs={'class': 'form-select'}),
'description': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'What was this for?'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['category'].queryset = Category.objects.all()

View File

@ -0,0 +1,40 @@
# Generated by Django 5.2.7 on 2026-02-01 13:44
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('type', models.CharField(choices=[('income', 'Income'), ('expense', 'Expense')], default='expense', max_length=10)),
('icon', models.CharField(blank=True, help_text='FontAwesome icon name (e.g., fa-utensils)', max_length=50, null=True)),
],
options={
'verbose_name_plural': 'Categories',
},
),
migrations.CreateModel(
name='Transaction',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=2, max_digits=10)),
('date', models.DateField(default=django.utils.timezone.now)),
('description', models.CharField(blank=True, max_length=255)),
('type', models.CharField(choices=[('income', 'Income'), ('expense', 'Expense')], default='expense', max_length=10)),
('created_at', models.DateTimeField(auto_now_add=True)),
('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transactions', to='core.category')),
],
),
]

View File

@ -1,3 +1,32 @@
from django.db import models
from django.utils import timezone
# Create your models here.
class Category(models.Model):
TRANSACTION_TYPES = [
('income', 'Income'),
('expense', 'Expense'),
]
name = models.CharField(max_length=100)
type = models.CharField(max_length=10, choices=TRANSACTION_TYPES, default='expense')
icon = models.CharField(max_length=50, blank=True, null=True, help_text="FontAwesome icon name (e.g., fa-utensils)")
class Meta:
verbose_name_plural = "Categories"
def __str__(self):
return f"{self.name} ({self.get_type_display()})"
class Transaction(models.Model):
TYPES = [
('income', 'Income'),
('expense', 'Expense'),
]
amount = models.DecimalField(max_digits=10, decimal_places=2)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='transactions')
date = models.DateField(default=timezone.now)
description = models.CharField(max_length=255, blank=True)
type = models.CharField(max_length=10, choices=TYPES, default='expense')
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.get_type_display()}: {self.amount} - {self.description[:20]}"

View File

@ -1,25 +1,72 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}Knowledge Base{% endblock %}</title>
{% if project_description %}
<meta name="description" content="{{ project_description }}">
<meta property="og:description" content="{{ project_description }}">
<meta property="twitter:description" content="{{ project_description }}">
{% endif %}
{% if project_image_url %}
<meta property="og:image" content="{{ project_image_url }}">
<meta property="twitter:image" content="{{ project_image_url }}">
{% endif %}
{% load static %}
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
{% block head %}{% endblock %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}BudgetTracker{% endblock %}</title>
{% if project_description %}
<meta name="description" content="{{ project_description }}">
<meta property="og:description" content="{{ project_description }}">
<meta property="twitter:description" content="{{ project_description }}">
{% endif %}
{% if project_image_url %}
<meta property="og:image" content="{{ project_image_url }}">
<meta property="twitter:image" content="{{ project_image_url }}">
{% endif %}
{% load static %}
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- FontAwesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
{% block head %}{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
</body>
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom sticky-top py-3">
<div class="container">
<a class="navbar-brand fw-bold d-flex align-items-center" href="{% url 'home' %}">
<div class="bg-primary bg-opacity-10 p-2 rounded-3 me-2">
<i class="fas fa-piggy-bank text-primary"></i>
</div>
BudgetTracker
</a>
<button class="navbar-toggler border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto align-items-center">
<li class="nav-item">
<a class="nav-link px-3" href="{% url 'home' %}">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link px-3" href="/admin/core/category/">Categories</a>
</li>
<li class="nav-item ms-lg-3">
<a class="btn btn-dark rounded-pill px-4" href="/admin/">
Admin <i class="fas fa-user-cog ms-1 small"></i>
</a>
</li>
</ul>
</div>
</div>
</nav>
</html>
<main>
{% block content %}{% endblock %}
</main>
<footer class="py-5 bg-white border-top mt-5">
<div class="container text-center">
<p class="text-muted mb-0">&copy; 2026 BudgetTracker. Built with Django & Flatlogic.</p>
</div>
</footer>
<!-- Bootstrap 5 Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{% block scripts %}{% endblock %}
</body>
</html>

View File

@ -1,145 +1,147 @@
{% extends "base.html" %}
{% block title %}{{ project_name }}{% endblock %}
{% block head %}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><path d='M-10 10L110 10M10 -10L10 110' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% {
background-position: 0% 0%;
}
100% {
background-position: 100% 100%;
}
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2.5rem 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
}
h1 {
font-size: clamp(2.2rem, 3vw + 1.2rem, 3.2rem);
font-weight: 700;
margin: 0 0 1.2rem;
letter-spacing: -0.02em;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
opacity: 0.92;
}
.loader {
margin: 1.5rem auto;
width: 56px;
height: 56px;
border: 4px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.runtime code {
background: rgba(0, 0, 0, 0.25);
padding: 0.15rem 0.45rem;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
footer {
position: absolute;
bottom: 1rem;
width: 100%;
text-align: center;
font-size: 0.85rem;
opacity: 0.75;
}
</style>
{% endblock %}
{% extends 'base.html' %}
{% load static %}
{% block content %}
<main>
<div class="card">
<h1>Analyzing your requirements and generating your app…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
<div class="hero-gradient">
<div class="container">
<div class="row align-items-center">
<div class="col-md-8">
<h1 class="display-5 mb-2">Hello! Here's your budget overview.</h1>
<p class="lead opacity-75">Tracking your finances for {{ current_month }}</p>
</div>
<div class="col-md-4 text-md-end">
<button class="btn btn-light btn-lg rounded-pill px-4 shadow-sm" data-bs-toggle="modal" data-bs-target="#addTransactionModal">
<i class="fas fa-plus me-2"></i> Add Transaction
</button>
</div>
</div>
</div>
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
<p class="runtime">
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
</p>
</div>
</main>
<footer>
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
</footer>
{% endblock %}
</div>
<div class="container py-5">
<!-- Stats Row -->
<div class="row g-4 mb-5">
<div class="col-md-4">
<div class="stats-card p-4">
<div class="d-flex align-items-center mb-3">
<div class="bg-primary bg-opacity-10 p-3 rounded-circle me-3">
<i class="fas fa-wallet text-primary fs-4"></i>
</div>
<span class="text-muted fw-medium">Total Balance</span>
</div>
<h2 class="mb-0 {% if balance >= 0 %}income-text{% else %}expense-text{% endif %}">
${{ balance|floatformat:2 }}
</h2>
</div>
</div>
<div class="col-md-4">
<div class="stats-card p-4">
<div class="d-flex align-items-center mb-3">
<div class="bg-success bg-opacity-10 p-3 rounded-circle me-3">
<i class="fas fa-arrow-down text-success fs-4"></i>
</div>
<span class="text-muted fw-medium">Monthly Income</span>
</div>
<h2 class="mb-0 income-text">${{ monthly_income|floatformat:2 }}</h2>
</div>
</div>
<div class="col-md-4">
<div class="stats-card p-4">
<div class="d-flex align-items-center mb-3">
<div class="bg-danger bg-opacity-10 p-3 rounded-circle me-3">
<i class="fas fa-arrow-up text-danger fs-4"></i>
</div>
<span class="text-muted fw-medium">Monthly Expenses</span>
</div>
<h2 class="mb-0 expense-text">${{ monthly_expense|floatformat:2 }}</h2>
</div>
</div>
</div>
<!-- Transactions List -->
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h3 class="mb-0">Recent Transactions</h3>
<a href="/admin/core/transaction/" class="text-decoration-none text-muted fw-medium">
View All <i class="fas fa-chevron-right ms-1"></i>
</a>
</div>
<div class="table-responsive">
<table class="table">
<tbody>
{% for transaction in recent_transactions %}
<tr class="transaction-row {{ transaction.type }} shadow-sm">
<td width="15%">
<div class="fw-bold">{{ transaction.date|date:"M d" }}</div>
<small class="text-muted">{{ transaction.date|date:"Y" }}</small>
</td>
<td>
<div class="fw-semibold">{{ transaction.description|default:"No description" }}</div>
<span class="badge bg-light text-dark rounded-pill">{{ transaction.category.name|default:"Uncategorized" }}</span>
</td>
<td class="text-end" width="20%">
<h5 class="mb-0 {% if transaction.type == 'income' %}income-text{% else %}expense-text{% endif %}">
{% if transaction.type == 'income' %}+{% else %}-{% endif %}${{ transaction.amount|floatformat:2 }}
</h5>
<small class="text-muted">{{ transaction.get_type_display }}</small>
</td>
</tr>
{% empty %}
<tr>
<td colspan="3" class="text-center py-5">
<img src="https://illustrations.popsy.co/gray/saving-money.svg" alt="Empty" style="width: 200px;" class="mb-3">
<p class="text-muted">No transactions yet. Start by adding one!</p>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Add Transaction Modal -->
<div class="modal fade" id="addTransactionModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow-lg" style="border-radius: 1.5rem;">
<div class="modal-header border-0 pb-0">
<h4 class="modal-title px-3 pt-3">New Entry</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-4">
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label class="form-label text-muted small text-uppercase fw-bold">Amount</label>
{{ form.amount }}
</div>
<div class="row mb-3">
<div class="col">
<label class="form-label text-muted small text-uppercase fw-bold">Type</label>
{{ form.type }}
</div>
<div class="col">
<label class="form-label text-muted small text-uppercase fw-bold">Category</label>
{{ form.category }}
</div>
</div>
<div class="mb-3">
<label class="form-label text-muted small text-uppercase fw-bold">Date</label>
{{ form.date }}
</div>
<div class="mb-4">
<label class="form-label text-muted small text-uppercase fw-bold">Description</label>
{{ form.description }}
</div>
<div class="px-2">
<button type="submit" class="btn btn-primary-gradient w-100 shadow">Save Transaction</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,25 +1,40 @@
import os
import platform
from django import get_version as django_version
from django.shortcuts import render
from django.shortcuts import render, redirect
from django.db.models import Sum
from django.utils import timezone
from .models import Transaction, Category
from .forms import TransactionForm
import datetime
def home(request):
"""Render the landing screen with loader and environment details."""
host_name = request.get_host().lower()
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
now = timezone.now()
today = timezone.now().date()
first_day_of_month = today.replace(day=1)
# Calculate stats
total_income = Transaction.objects.filter(type='income').aggregate(Sum('amount'))['amount__sum'] or 0
total_expense = Transaction.objects.filter(type='expense').aggregate(Sum('amount'))['amount__sum'] or 0
balance = total_income - total_expense
monthly_income = Transaction.objects.filter(type='income', date__gte=first_day_of_month).aggregate(Sum('amount'))['amount__sum'] or 0
monthly_expense = Transaction.objects.filter(type='expense', date__gte=first_day_of_month).aggregate(Sum('amount'))['amount__sum'] or 0
recent_transactions = Transaction.objects.select_related('category').order_by('-date', '-created_at')[:10]
if request.method == 'POST':
form = TransactionForm(request.POST)
if form.is_valid():
form.save()
return redirect('home')
else:
form = TransactionForm()
context = {
"project_name": "New Style",
"agent_brand": agent_brand,
"django_version": django_version(),
"python_version": platform.python_version(),
"current_time": now,
"host_name": host_name,
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
'total_income': total_income,
'total_expense': total_expense,
'balance': balance,
'monthly_income': monthly_income,
'monthly_expense': monthly_expense,
'recent_transactions': recent_transactions,
'form': form,
'current_month': today.strftime('%B %Y'),
}
return render(request, "core/index.html", context)
return render(request, 'core/index.html', context)

View File

@ -1,4 +1,86 @@
/* Custom styles for the application */
body {
font-family: system-ui, -apple-system, sans-serif;
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Outfit:wght@600;700&display=swap');
:root {
--bg-color: #F8FAFC;
--slate-800: #1E293B;
--slate-500: #64748B;
--emerald: #10B981;
--rose: #F43F5E;
--indigo: #6366F1;
--violet: #8B5CF6;
}
body {
background-color: var(--bg-color);
font-family: 'Inter', sans-serif;
color: var(--slate-800);
}
h1, h2, h3, .h1, .h2, .h3 {
font-family: 'Outfit', sans-serif;
font-weight: 700;
}
.hero-gradient {
background: linear-gradient(135deg, var(--indigo) 0%, var(--violet) 100%);
color: white;
padding: 3rem 0;
border-radius: 0 0 2rem 2rem;
margin-bottom: -4rem;
}
.stats-card {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 1.25rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
transition: transform 0.2s ease;
}
.stats-card:hover {
transform: translateY(-2px);
}
.income-text { color: var(--emerald); }
.expense-text { color: var(--rose); }
.btn-primary-gradient {
background: linear-gradient(135deg, var(--indigo) 0%, var(--violet) 100%);
border: none;
color: white;
font-weight: 600;
padding: 0.75rem 1.5rem;
border-radius: 0.75rem;
}
.btn-primary-gradient:hover {
opacity: 0.9;
color: white;
}
.transaction-row {
border-left: 4px solid transparent;
transition: all 0.2s ease;
}
.transaction-row.income { border-left-color: var(--emerald); }
.transaction-row.expense { border-left-color: var(--rose); }
.table {
border-collapse: separate;
border-spacing: 0 0.5rem;
}
.table tr {
background: white;
border-radius: 0.75rem;
}
.table td {
padding: 1rem;
vertical-align: middle;
}
.table td:first-child { border-radius: 0.75rem 0 0 0.75rem; }
.table td:last-child { border-radius: 0 0.75rem 0.75rem 0; }

View File

@ -1,21 +1,86 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Outfit:wght@600;700&display=swap');
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
--bg-color: #F8FAFC;
--slate-800: #1E293B;
--slate-500: #64748B;
--emerald: #10B981;
--rose: #F43F5E;
--indigo: #6366F1;
--violet: #8B5CF6;
}
body {
margin: 0;
background-color: var(--bg-color);
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
color: var(--slate-800);
}
h1, h2, h3, .h1, .h2, .h3 {
font-family: 'Outfit', sans-serif;
font-weight: 700;
}
.hero-gradient {
background: linear-gradient(135deg, var(--indigo) 0%, var(--violet) 100%);
color: white;
padding: 3rem 0;
border-radius: 0 0 2rem 2rem;
margin-bottom: -4rem;
}
.stats-card {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 1.25rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
transition: transform 0.2s ease;
}
.stats-card:hover {
transform: translateY(-2px);
}
.income-text { color: var(--emerald); }
.expense-text { color: var(--rose); }
.btn-primary-gradient {
background: linear-gradient(135deg, var(--indigo) 0%, var(--violet) 100%);
border: none;
color: white;
font-weight: 600;
padding: 0.75rem 1.5rem;
border-radius: 0.75rem;
}
.btn-primary-gradient:hover {
opacity: 0.9;
color: white;
}
.transaction-row {
border-left: 4px solid transparent;
transition: all 0.2s ease;
}
.transaction-row.income { border-left-color: var(--emerald); }
.transaction-row.expense { border-left-color: var(--rose); }
.table {
border-collapse: separate;
border-spacing: 0 0.5rem;
}
.table tr {
background: white;
border-radius: 0.75rem;
}
.table td {
padding: 1rem;
vertical-align: middle;
}
.table td:first-child { border-radius: 0.75rem 0 0 0.75rem; }
.table td:last-child { border-radius: 0 0.75rem 0.75rem 0; }