diff --git a/assets/pasted-20251025-220554-44ea9dbe.png b/assets/pasted-20251025-220554-44ea9dbe.png new file mode 100644 index 0000000..b1db423 Binary files /dev/null and b/assets/pasted-20251025-220554-44ea9dbe.png differ diff --git a/assets/pasted-20251025-221111-267601dc.png b/assets/pasted-20251025-221111-267601dc.png new file mode 100644 index 0000000..d419ba0 Binary files /dev/null and b/assets/pasted-20251025-221111-267601dc.png differ diff --git a/assets/pasted-20251025-221630-bbdf366c.png b/assets/pasted-20251025-221630-bbdf366c.png new file mode 100644 index 0000000..4ece587 Binary files /dev/null and b/assets/pasted-20251025-221630-bbdf366c.png differ diff --git a/assets/pasted-20251025-221947-7b8e5858.png b/assets/pasted-20251025-221947-7b8e5858.png new file mode 100644 index 0000000..c71c855 Binary files /dev/null and b/assets/pasted-20251025-221947-7b8e5858.png differ diff --git a/assets/pasted-20251025-222540-5c9b518a.png b/assets/pasted-20251025-222540-5c9b518a.png new file mode 100644 index 0000000..0259b59 Binary files /dev/null and b/assets/pasted-20251025-222540-5c9b518a.png differ diff --git a/config/__pycache__/__init__.cpython-311.pyc b/config/__pycache__/__init__.cpython-311.pyc index 3d6501c..27c9762 100644 Binary files a/config/__pycache__/__init__.cpython-311.pyc and b/config/__pycache__/__init__.cpython-311.pyc differ diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index dadfaa7..a6e30a0 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 139db10..c75accc 100644 Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ diff --git a/config/__pycache__/wsgi.cpython-311.pyc b/config/__pycache__/wsgi.cpython-311.pyc index 79ce690..5ae13f2 100644 Binary files a/config/__pycache__/wsgi.cpython-311.pyc and b/config/__pycache__/wsgi.cpython-311.pyc differ diff --git a/config/settings.py b/config/settings.py index 001b8c8..a7247c1 100644 --- a/config/settings.py +++ b/config/settings.py @@ -141,6 +141,9 @@ USE_TZ = True STATIC_URL = '/static/' STATIC_ROOT = BASE_DIR / 'staticfiles' +MEDIA_URL = '/media/' +MEDIA_ROOT = BASE_DIR / 'media' + STATICFILES_DIRS = [ BASE_DIR / 'static', diff --git a/config/urls.py b/config/urls.py index 5093d47..f7273ed 100644 --- a/config/urls.py +++ b/config/urls.py @@ -16,8 +16,13 @@ Including another URLconf """ from django.contrib import admin from django.urls import include, path +from django.conf import settings +from django.conf.urls.static import static urlpatterns = [ path("admin/", admin.site.urls), path("", include("core.urls")), ] + +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/core/__pycache__/__init__.cpython-311.pyc b/core/__pycache__/__init__.cpython-311.pyc index 3b7774e..e48b694 100644 Binary files a/core/__pycache__/__init__.cpython-311.pyc and b/core/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 5e41572..b5d42f4 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/apps.cpython-311.pyc b/core/__pycache__/apps.cpython-311.pyc index 6435d92..f887de2 100644 Binary files a/core/__pycache__/apps.cpython-311.pyc and b/core/__pycache__/apps.cpython-311.pyc differ diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc index f6e5c4e..5588f78 100644 Binary files a/core/__pycache__/forms.cpython-311.pyc and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 5b41fe1..e2edb4b 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 4e4f113..ed85e77 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 9d0ddd8..9e356d7 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 639ff3a..3510ab0 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,8 +1,49 @@ from django.contrib import admin -from .models import Ticket +from .models import Ticket, Client, Portfolio, Activity, Document +import secrets +import string @admin.register(Ticket) class TicketAdmin(admin.ModelAdmin): list_display = ('subject', 'status', 'priority', 'requester_email', 'created_at') list_filter = ('status', 'priority') search_fields = ('subject', 'requester_email', 'description') + +@admin.register(Client) +class ClientAdmin(admin.ModelAdmin): + list_display = ('name', 'surname', 'email', 'phone', 'created_at') + search_fields = ('name', 'surname', 'email') + readonly_fields = ('generated_password',) + fieldsets = ( + (None, { + 'fields': ('name', 'surname', 'email', 'phone', 'address', 'fiscal_code', 'vat_number') + }), + ('Credenziali', { + 'fields': ('generated_password',) + }), + ) + + def save_model(self, request, obj, form, change): + if not obj.pk: # Only generate password for new clients + alphabet = string.ascii_letters + string.digits + password = ''.join(secrets.choice(alphabet) for i in range(12)) + obj.generated_password = password + super().save_model(request, obj, form, change) + +@admin.register(Portfolio) +class PortfolioAdmin(admin.ModelAdmin): + list_display = ('name', 'client', 'created_at') + search_fields = ('name', 'client__name', 'client__surname') + list_filter = ('client',) + +@admin.register(Activity) +class ActivityAdmin(admin.ModelAdmin): + list_display = ('title', 'client', 'status', 'activity_date', 'created_at') + search_fields = ('title', 'client__name', 'client__surname') + list_filter = ('status', 'activity_date', 'client') + +@admin.register(Document) +class DocumentAdmin(admin.ModelAdmin): + list_display = ('title', 'client', 'created_at') + search_fields = ('title', 'client__name', 'client__surname') + list_filter = ('client',) diff --git a/core/forms.py b/core/forms.py index 7a6b83b..d15cc9f 100644 --- a/core/forms.py +++ b/core/forms.py @@ -1,7 +1,31 @@ from django import forms -from .models import Ticket +from .models import Document, Client, Portfolio, Activity -class TicketForm(forms.ModelForm): +class DocumentForm(forms.ModelForm): class Meta: - model = Ticket - fields = ['subject', 'requester_email', 'priority', 'description'] + model = Document + fields = ('client', 'title', 'description', 'file') + widgets = { + 'client': forms.Select(attrs={'class': 'form-control'}), + 'title': forms.TextInput(attrs={'class': 'form-control'}), + 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), + 'file': forms.FileInput(attrs={'class': 'form-control'}), + } + + +class ClientForm(forms.ModelForm): + class Meta: + model = Client + fields = ('name', 'email', 'phone', 'address') + + +class PortfolioForm(forms.ModelForm): + class Meta: + model = Portfolio + fields = ('client', 'name', 'description') + + +class ActivityForm(forms.ModelForm): + class Meta: + model = Activity + fields = ('client', 'title', 'description', 'status', 'activity_date') diff --git a/core/migrations/0002_client.py b/core/migrations/0002_client.py new file mode 100644 index 0000000..d2d6a02 --- /dev/null +++ b/core/migrations/0002_client.py @@ -0,0 +1,34 @@ +# Generated by Django 5.2.7 on 2025-10-25 21:35 + +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Client', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100, verbose_name='Nome')), + ('surname', models.CharField(max_length=100, verbose_name='Cognome')), + ('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')), + ('phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='Telefono')), + ('address', models.CharField(blank=True, max_length=255, null=True, verbose_name='Indirizzo')), + ('fiscal_code', models.CharField(blank=True, max_length=16, null=True, verbose_name='Codice Fiscale')), + ('vat_number', models.CharField(blank=True, max_length=11, null=True, verbose_name='Partita IVA')), + ('generated_password', models.CharField(help_text='Password generata per il cliente (visibile in chiaro)', max_length=128)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + options={ + 'verbose_name': 'Cliente', + 'verbose_name_plural': 'Clienti', + }, + ), + ] diff --git a/core/migrations/0003_portfolio.py b/core/migrations/0003_portfolio.py new file mode 100644 index 0000000..457657d --- /dev/null +++ b/core/migrations/0003_portfolio.py @@ -0,0 +1,30 @@ +# Generated by Django 5.2.7 on 2025-10-25 21:42 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0002_client'), + ] + + operations = [ + migrations.CreateModel( + name='Portfolio', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100, verbose_name='Nome Portafoglio')), + ('description', models.TextField(blank=True, null=True, verbose_name='Descrizione')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='portfolios', to='core.client', verbose_name='Cliente')), + ], + options={ + 'verbose_name': 'Portafoglio', + 'verbose_name_plural': 'Portafogli', + }, + ), + ] diff --git a/core/migrations/0004_activity.py b/core/migrations/0004_activity.py new file mode 100644 index 0000000..8e86527 --- /dev/null +++ b/core/migrations/0004_activity.py @@ -0,0 +1,33 @@ +# Generated by Django 5.2.7 on 2025-10-25 21:44 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0003_portfolio'), + ] + + operations = [ + migrations.CreateModel( + name='Activity', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('title', models.CharField(max_length=200, verbose_name='Titolo')), + ('description', models.TextField(blank=True, null=True, verbose_name='Descrizione')), + ('status', models.CharField(choices=[('todo', 'To Do'), ('in_progress', 'In Progress'), ('done', 'Done')], default='todo', max_length=20, verbose_name='Stato')), + ('activity_date', models.DateField(verbose_name='Data Attività')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activities', to='core.client', verbose_name='Cliente')), + ], + options={ + 'verbose_name': 'Attività', + 'verbose_name_plural': 'Attività', + 'ordering': ['-activity_date'], + }, + ), + ] diff --git a/core/migrations/0005_document.py b/core/migrations/0005_document.py new file mode 100644 index 0000000..59b3a2a --- /dev/null +++ b/core/migrations/0005_document.py @@ -0,0 +1,33 @@ +# Generated by Django 5.2.7 on 2025-10-25 21:46 + +import core.models +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0004_activity'), + ] + + operations = [ + migrations.CreateModel( + name='Document', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('title', models.CharField(max_length=200, verbose_name='Titolo')), + ('description', models.TextField(blank=True, null=True, verbose_name='Descrizione')), + ('file', models.FileField(upload_to=core.models.client_directory_path, verbose_name='File')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='core.client', verbose_name='Cliente')), + ], + options={ + 'verbose_name': 'Documento', + 'verbose_name_plural': 'Documenti', + 'ordering': ['-created_at'], + }, + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc index 64d8a55..6e80b6b 100644 Binary files a/core/migrations/__pycache__/0001_initial.cpython-311.pyc and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0002_client.cpython-311.pyc b/core/migrations/__pycache__/0002_client.cpython-311.pyc new file mode 100644 index 0000000..978a5c6 Binary files /dev/null and b/core/migrations/__pycache__/0002_client.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0003_portfolio.cpython-311.pyc b/core/migrations/__pycache__/0003_portfolio.cpython-311.pyc new file mode 100644 index 0000000..642ea8d Binary files /dev/null and b/core/migrations/__pycache__/0003_portfolio.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0004_activity.cpython-311.pyc b/core/migrations/__pycache__/0004_activity.cpython-311.pyc new file mode 100644 index 0000000..495a467 Binary files /dev/null and b/core/migrations/__pycache__/0004_activity.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0005_document.cpython-311.pyc b/core/migrations/__pycache__/0005_document.cpython-311.pyc new file mode 100644 index 0000000..71b285e Binary files /dev/null and b/core/migrations/__pycache__/0005_document.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/__init__.cpython-311.pyc b/core/migrations/__pycache__/__init__.cpython-311.pyc index 58b1c14..015c09e 100644 Binary files a/core/migrations/__pycache__/__init__.cpython-311.pyc and b/core/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 78b60d1..6656af9 100644 --- a/core/models.py +++ b/core/models.py @@ -1,4 +1,5 @@ from django.db import models +import uuid class Ticket(models.Model): STATUS_CHOICES = [ @@ -22,4 +23,84 @@ class Ticket(models.Model): updated_at = models.DateTimeField(auto_now=True) def __str__(self): - return self.subject \ No newline at end of file + return self.subject + +class Client(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + name = models.CharField(max_length=100, verbose_name="Nome") + surname = models.CharField(max_length=100, verbose_name="Cognome") + email = models.EmailField(unique=True, verbose_name="Email") + phone = models.CharField(max_length=20, blank=True, null=True, verbose_name="Telefono") + address = models.CharField(max_length=255, blank=True, null=True, verbose_name="Indirizzo") + fiscal_code = models.CharField(max_length=16, blank=True, null=True, verbose_name="Codice Fiscale") + vat_number = models.CharField(max_length=11, blank=True, null=True, verbose_name="Partita IVA") + generated_password = models.CharField(max_length=128, help_text="Password generata per il cliente (visibile in chiaro)") + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + verbose_name = "Cliente" + verbose_name_plural = "Clienti" + + def __str__(self): + return f"{self.name} {self.surname}" + +class Portfolio(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + name = models.CharField(max_length=100, verbose_name="Nome Portafoglio") + description = models.TextField(blank=True, null=True, verbose_name="Descrizione") + client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name='portfolios', verbose_name="Cliente") + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + verbose_name = "Portafoglio" + verbose_name_plural = "Portafogli" + + def __str__(self): + return f"{self.name} ({self.client})" + +class Activity(models.Model): + STATUS_CHOICES = [ + ('todo', 'To Do'), + ('in_progress', 'In Progress'), + ('done', 'Done'), + ] + + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + title = models.CharField(max_length=200, verbose_name="Titolo") + description = models.TextField(blank=True, null=True, verbose_name="Descrizione") + client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name='activities', verbose_name="Cliente") + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='todo', verbose_name="Stato") + activity_date = models.DateField(verbose_name="Data Attività") + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + verbose_name = "Attività" + verbose_name_plural = "Attività" + ordering = ['-activity_date'] + + def __str__(self): + return self.title + +def client_directory_path(instance, filename): + # file will be uploaded to MEDIA_ROOT/documents// + return f'documents/{instance.client.id}/{filename}' + +class Document(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + title = models.CharField(max_length=200, verbose_name="Titolo") + description = models.TextField(blank=True, null=True, verbose_name="Descrizione") + client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name='documents', verbose_name="Cliente") + file = models.FileField(upload_to=client_directory_path, verbose_name="File") + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + verbose_name = "Documento" + verbose_name_plural = "Documenti" + ordering = ['-created_at'] + + def __str__(self): + return self.title diff --git a/core/templates/base.html b/core/templates/base.html new file mode 100644 index 0000000..0bdfce1 --- /dev/null +++ b/core/templates/base.html @@ -0,0 +1,55 @@ + + + + + + {% block title %}Finance Hub{% endblock %} + + + + + {% load static %} + + + {% block head %}{% endblock %} + + + + +
+
+
+ {% block content %}{% endblock %} +
+
+
+ + + \ No newline at end of file diff --git a/core/templates/core/activity_confirm_delete.html b/core/templates/core/activity_confirm_delete.html new file mode 100644 index 0000000..c5b34dc --- /dev/null +++ b/core/templates/core/activity_confirm_delete.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Delete Activity

+

Are you sure you want to delete "{{ object }}"?

+
+ {% csrf_token %} + + Cancel +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/activity_detail.html b/core/templates/core/activity_detail.html new file mode 100644 index 0000000..65600cb --- /dev/null +++ b/core/templates/core/activity_detail.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block content %} +
+

Dettaglio Attività

+
+ Modifica + Elimina +
+
+ +
+
+

{{ activity.title }}

+
+
+

Cliente: {{ activity.client.name }}

+

Stato: {{ activity.get_status_display }}

+

Descrizione:

+

{{ activity.description|linebreaksbr }}

+

Data Creazione: {{ activity.created_at|date:"d/m/Y H:i" }}

+
+ +
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/activity_form.html b/core/templates/core/activity_form.html new file mode 100644 index 0000000..65f58e6 --- /dev/null +++ b/core/templates/core/activity_form.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} + +{% block content %} +
+

{{ title }}

+
+ {% csrf_token %} + {{ form.as_p }} + + {% if form.instance.pk %} + Cancel + {% else %} + Cancel + {% endif %} +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/activity_list.html b/core/templates/core/activity_list.html new file mode 100644 index 0000000..85cb7ae --- /dev/null +++ b/core/templates/core/activity_list.html @@ -0,0 +1,43 @@ +{% extends "base.html" %} + +{% block content %} +
+

Attività

+ Aggiungi Attività +
+ +
+
+
+ + + + + + + + + + + + {% for activity in activities %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
TitoloClienteStatoData Creazione
{{ activity.title }}{{ activity.client.name }}{{ activity.get_status_display }}{{ activity.created_at|date:"d/m/Y" }} + Dettagli +
Nessuna attività trovata.
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/article_detail.html b/core/templates/core/article_detail.html new file mode 100644 index 0000000..8820990 --- /dev/null +++ b/core/templates/core/article_detail.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} + +{% block title %}{{ article.title }}{% endblock %} + +{% block content %} +
+

{{ article.title }}

+

Published on {{ article.created_at|date:"F d, Y" }}

+
+
+ {{ article.content|safe }} +
+
+{% endblock %} diff --git a/core/templates/core/client_confirm_delete.html b/core/templates/core/client_confirm_delete.html new file mode 100644 index 0000000..7ed77d7 --- /dev/null +++ b/core/templates/core/client_confirm_delete.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Delete Client

+

Are you sure you want to delete "{{ object }}"?

+
+ {% csrf_token %} + + Cancel +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/client_detail.html b/core/templates/core/client_detail.html new file mode 100644 index 0000000..d99b315 --- /dev/null +++ b/core/templates/core/client_detail.html @@ -0,0 +1,61 @@ +{% extends "base.html" %} + +{% block content %} +
+

Dettaglio Cliente

+
+ Modifica + Elimina +
+
+ +
+
+

{{ client.name }}

+
+
+

Email: {{ client.email }}

+

Telefono: {{ client.phone }}

+

Indirizzo: {{ client.address }}

+

Password di sistema: {{ client.system_password }}

+

Data Creazione: {{ client.created_at|date:"d/m/Y H:i" }}

+
+
+ +
+
+

Attività del Cliente

+
+
+ + + + + + + + + + + {% for activity in activities %} + + + + + + + {% empty %} + + + + {% endfor %} + +
TitoloStatoDataAzioni
{{ activity.title }}{{ activity.get_status_display }}{{ activity.activity_date|date:"d/m/Y" }} + Dettagli +
Nessuna attività trovata per questo cliente.
+
+ +
+{% endblock %} diff --git a/core/templates/core/client_form.html b/core/templates/core/client_form.html new file mode 100644 index 0000000..c0495cf --- /dev/null +++ b/core/templates/core/client_form.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} + +{% block content %} +
+

{{ title }}

+
+ {% csrf_token %} + {{ form.as_p }} + + {% if form.instance.pk %} + Cancel + {% else %} + Cancel + {% endif %} +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/client_list.html b/core/templates/core/client_list.html new file mode 100644 index 0000000..07a7a3d --- /dev/null +++ b/core/templates/core/client_list.html @@ -0,0 +1,43 @@ +{% extends "base.html" %} + +{% block content %} +
+

Clienti

+ Aggiungi Cliente +
+ +
+
+
+ + + + + + + + + + + + {% for client in clients %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
NomeEmailTelefonoData Creazione
{{ client.name }}{{ client.email }}{{ client.phone }}{{ client.created_at|date:"d/m/Y" }} + Dettagli +
Nessun cliente trovato.
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/document_detail.html b/core/templates/core/document_detail.html new file mode 100644 index 0000000..963f8e4 --- /dev/null +++ b/core/templates/core/document_detail.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} + +{% block content %} +
+

Dettaglio Documento

+
+ Elimina +
+
+ +
+
+

{{ document.name }}

+
+
+

Cliente: {{ document.client.name }}

+

Descrizione:

+

{{ document.description|linebreaksbr }}

+

Data Caricamento: {{ document.created_at|date:"d/m/Y H:i" }}

+

File: Visualizza/Scarica

+
+ +
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/document_list.html b/core/templates/core/document_list.html new file mode 100644 index 0000000..041a614 --- /dev/null +++ b/core/templates/core/document_list.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} + +{% block content %} +
+

Documenti

+ Carica Documento +
+ +
+
+
+ + + + + + + + + + + {% for document in documents %} + + + + + + + {% empty %} + + + + {% endfor %} + +
NomeClienteData Caricamento
{{ document.name }}{{ document.client.name }}{{ document.created_at|date:"d/m/Y" }} + Dettagli +
Nessun documento trovato.
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/document_upload.html b/core/templates/core/document_upload.html new file mode 100644 index 0000000..a6b8cff --- /dev/null +++ b/core/templates/core/document_upload.html @@ -0,0 +1,34 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+
+

Carica Nuovo Documento

+
+
+
+ {% csrf_token %} +
+ + {{ form.client }} +
+
+ + {{ form.title }} +
+
+ + {{ form.description }} +
+
+ + {{ form.file }} +
+ + Annulla +
+
+
+
+{% endblock %} diff --git a/core/templates/core/index.html b/core/templates/core/index.html index f4e4991..4498fb8 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,157 +1,94 @@ - - +{% extends 'base.html' %} - - - - {{ project_name }} - {% if project_description %} - - - - {% endif %} - {% if project_image_url %} - - - {% endif %} - - - - - - - -
-
-

Analyzing your requirements and generating your website…

-
- Loading… + +
+
+
+
+
Totale Clienti
+

{{ kpi_data.total_clients }}

-

Appwizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} — - UTC {{ current_time|date:"Y-m-d H:i:s" }} -

-
- Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC) -
-
- + +
+
+
+
Totale Portafogli
+

{{ kpi_data.total_portfolios }}

+
+
+
+
+
+
+
Totale Attività
+

{{ kpi_data.total_activities }}

+
+
+
+
+
+
+
Totale Documenti
+

{{ kpi_data.total_documents }}

+
+
+
+ - \ No newline at end of file + +
+
+
+
+ Clienti Recenti + Vedi Tutti +
+
+ {% if recent_clients %} +
    + {% for client in recent_clients %} +
  • + {{ client.name }} {{ client.surname }} + Dettagli +
  • + {% endfor %} +
+ {% else %} +

Nessun cliente recente.

+ {% endif %} +
+
+
+
+
+
+ Attività Recenti + Vedi Tutte +
+
+ {% if recent_activities %} +
    + {% for activity in recent_activities %} +
  • +
    + {{ activity.title }}
    + {{ activity.client }} - {{ activity.activity_date|date:"d M Y" }} +
    + Dettagli +
  • + {% endfor %} +
+ {% else %} +

Nessuna attività recente.

+ {% endif %} +
+
+
+
+{% endblock %} diff --git a/core/templates/core/portfolio_confirm_delete.html b/core/templates/core/portfolio_confirm_delete.html new file mode 100644 index 0000000..bf2e01a --- /dev/null +++ b/core/templates/core/portfolio_confirm_delete.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Delete Portfolio

+

Are you sure you want to delete "{{ object }}"?

+
+ {% csrf_token %} + + Cancel +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/portfolio_detail.html b/core/templates/core/portfolio_detail.html new file mode 100644 index 0000000..fb658a0 --- /dev/null +++ b/core/templates/core/portfolio_detail.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} + +{% block content %} +
+

Dettaglio Portafoglio

+
+ Modifica + Elimina +
+
+ +
+
+

{{ portfolio.name }}

+
+
+

Cliente: {{ portfolio.client.name }}

+

Descrizione:

+

{{ portfolio.description|linebreaksbr }}

+

Data Creazione: {{ portfolio.created_at|date:"d/m/Y H:i" }}

+
+ +
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/portfolio_form.html b/core/templates/core/portfolio_form.html new file mode 100644 index 0000000..dfc8008 --- /dev/null +++ b/core/templates/core/portfolio_form.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} + +{% block content %} +
+

{{ title }}

+
+ {% csrf_token %} + {{ form.as_p }} + + {% if form.instance.pk %} + Cancel + {% else %} + Cancel + {% endif %} +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/portfolio_list.html b/core/templates/core/portfolio_list.html new file mode 100644 index 0000000..675c906 --- /dev/null +++ b/core/templates/core/portfolio_list.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} + +{% block content %} +
+

Portafogli

+ Aggiungi Portafoglio +
+ +
+
+
+ + + + + + + + + + + {% for portfolio in portfolios %} + + + + + + + {% empty %} + + + + {% endfor %} + +
NomeClienteData Creazione
{{ portfolio.name }}{{ portfolio.client.name }}{{ portfolio.created_at|date:"d/m/Y" }} + Dettagli +
Nessun portafoglio trovato.
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/urls.py b/core/urls.py index 6299e3d..641ddee 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,47 @@ from django.urls import path -from .views import home +from .views import ( + index, + client_list, + client_detail, + client_create, + client_update, + client_delete, + portfolio_list, + portfolio_detail, + portfolio_create, + portfolio_update, + portfolio_delete, + activity_list, + activity_detail, + activity_create, + activity_update, + activity_delete, + document_list, + document_detail, + document_upload, + document_delete +) urlpatterns = [ - path("", home, name="home"), -] + path("", index, name="home"), + path("clients/", client_list, name="client_list"), + path("clients/add/", client_create, name="client_create"), + path("clients//", client_detail, name="client_detail"), + path("clients//edit/", client_update, name="client_update"), + path("clients//delete/", client_delete, name="client_delete"), + path("portfolios/", portfolio_list, name="portfolio_list"), + path("portfolios/add/", portfolio_create, name="portfolio_create"), + path("portfolios//", portfolio_detail, name="portfolio_detail"), + path("portfolios//edit/", portfolio_update, name="portfolio_update"), + path("portfolios//delete/", portfolio_delete, name="portfolio_delete"), + path("activities/", activity_list, name="activity_list"), + path("activities/add/", activity_create, name="activity_create"), + path("activities//", activity_detail, name="activity_detail"), + path("activities//edit/", activity_update, name="activity_update"), + path("activities//delete/", activity_delete, name="activity_delete"), + path("documents/", document_list, name="document_list"), + path("documents/upload/", document_upload, name="document_upload"), + path("documents//", document_detail, name="document_detail"), + path("documents//delete/", document_delete, name="document_delete"), +] \ No newline at end of file diff --git a/core/views.py b/core/views.py index c1a6d45..263e914 100644 --- a/core/views.py +++ b/core/views.py @@ -1,37 +1,206 @@ -import os -import platform +from django.shortcuts import render, get_object_or_404, redirect +from .models import Client, Portfolio, Activity, Document +from .forms import DocumentForm, ClientForm, PortfolioForm, ActivityForm -from django import get_version as django_version -from django.shortcuts import render -from django.urls import reverse_lazy -from django.utils import timezone -from django.views.generic.edit import CreateView +def index(request): + """Render the main dashboard screen.""" + # Placeholder data for the KPI cards + kpi_data = { + "total_clients": Client.objects.count(), + "total_portfolios": Portfolio.objects.count(), + "total_activities": Activity.objects.count(), + "total_documents": Document.objects.count(), + "pending_tasks": Activity.objects.filter(status='todo').count(), + } -from .forms import TicketForm -from .models import Ticket - - -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() + # Fetch recent items for the dashboard widgets + recent_clients = Client.objects.order_by('-created_at')[:5] + recent_activities = Activity.objects.order_by('-created_at')[:5] 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", ""), + "project_name": "Finance Hub", + "kpi_data": kpi_data, + "recent_clients": recent_clients, + "recent_activities": recent_activities, } return render(request, "core/index.html", context) +def client_list(request): + """Render a page with a list of all clients.""" + clients = Client.objects.all() + context = { + "clients": clients, + } + return render(request, "core/client_list.html", context) -class TicketCreateView(CreateView): - model = Ticket - form_class = TicketForm - template_name = "core/ticket_create.html" - success_url = reverse_lazy("home") +def client_detail(request, pk): + """Render a page with the details of a single client.""" + client = get_object_or_404(Client, pk=pk) + activities = client.activities.all() + context = { + "client": client, + "activities": activities, + } + return render(request, "core/client_detail.html", context) + +def portfolio_list(request): + """Render a page with a list of all portfolios.""" + portfolios = Portfolio.objects.all() + context = { + "portfolios": portfolios, + } + return render(request, "core/portfolio_list.html", context) + +def portfolio_detail(request, pk): + """Render a page with the details of a single portfolio.""" + portfolio = get_object_or_404(Portfolio, pk=pk) + context = { + "portfolio": portfolio, + } + return render(request, "core/portfolio_detail.html", context) + +def activity_list(request): + """Render a page with a list of all activities.""" + activities = Activity.objects.all() + context = { + "activities": activities, + } + return render(request, "core/activity_list.html", context) + +def activity_detail(request, pk): + """Render a page with the details of a single activity.""" + activity = get_object_or_404(Activity, pk=pk) + context = { + "activity": activity, + } + return render(request, "core/activity_detail.html", context) + +def document_list(request): + """Render a page with a list of all documents.""" + documents = Document.objects.all() + context = { + "documents": documents, + } + return render(request, "core/document_list.html", context) + +def document_detail(request, pk): + """Render a page with the details of a single document.""" + document = get_object_or_404(Document, pk=pk) + context = { + "document": document, + } + return render(request, "core/document_detail.html", context) + +def document_upload(request): + """Handle document upload.""" + if request.method == 'POST': + form = DocumentForm(request.POST, request.FILES) + if form.is_valid(): + form.save() + return redirect('document_list') + else: + form = DocumentForm() + context = { + 'form': form, + } + return render(request, 'core/document_upload.html', context) + + +def client_create(request): + if request.method == 'POST': + form = ClientForm(request.POST) + if form.is_valid(): + form.save() + return redirect('client_list') + else: + form = ClientForm() + return render(request, 'core/client_form.html', {'form': form, 'title': 'Add Client'}) + + +def client_update(request, pk): + client = get_object_or_404(Client, pk=pk) + if request.method == 'POST': + form = ClientForm(request.POST, instance=client) + if form.is_valid(): + form.save() + return redirect('client_detail', pk=client.pk) + else: + form = ClientForm(instance=client) + return render(request, 'core/client_form.html', {'form': form, 'title': 'Edit Client'}) + + +def client_delete(request, pk): + client = get_object_or_404(Client, pk=pk) + if request.method == 'POST': + client.delete() + return redirect('client_list') + return render(request, 'core/client_confirm_delete.html', {'object': client}) + + +def portfolio_create(request): + if request.method == 'POST': + form = PortfolioForm(request.POST) + if form.is_valid(): + form.save() + return redirect('portfolio_list') + else: + form = PortfolioForm() + return render(request, 'core/portfolio_form.html', {'form': form, 'title': 'Add Portfolio'}) + + +def portfolio_update(request, pk): + portfolio = get_object_or_404(Portfolio, pk=pk) + if request.method == 'POST': + form = PortfolioForm(request.POST, instance=portfolio) + if form.is_valid(): + form.save() + return redirect('portfolio_detail', pk=portfolio.pk) + else: + form = PortfolioForm(instance=portfolio) + return render(request, 'core/portfolio_form.html', {'form': form, 'title': 'Edit Portfolio'}) + + +def portfolio_delete(request, pk): + portfolio = get_object_or_404(Portfolio, pk=pk) + if request.method == 'POST': + portfolio.delete() + return redirect('portfolio_list') + return render(request, 'core/portfolio_confirm_delete.html', {'object': portfolio}) + + +def activity_create(request): + if request.method == 'POST': + form = ActivityForm(request.POST) + if form.is_valid(): + form.save() + return redirect('activity_list') + else: + form = ActivityForm() + return render(request, 'core/activity_form.html', {'form': form, 'title': 'Add Activity'}) + + +def activity_update(request, pk): + activity = get_object_or_404(Activity, pk=pk) + if request.method == 'POST': + form = ActivityForm(request.POST, instance=activity) + if form.is_valid(): + form.save() + return redirect('activity_detail', pk=activity.pk) + else: + form = ActivityForm(instance=activity) + return render(request, 'core/activity_form.html', {'form': form, 'title': 'Edit Activity'}) + + +def activity_delete(request, pk): + activity = get_object_or_404(Activity, pk=pk) + if request.method == 'POST': + activity.delete() + return redirect('activity_list') + return render(request, 'core/activity_confirm_delete.html', {'object': activity}) + +def document_delete(request, pk): + document = get_object_or_404(Document, pk=pk) + if request.method == 'POST': + document.delete() + return redirect('document_list') + return render(request, 'core/document_confirm_delete.html', {'object': document}) diff --git a/media/documents/205a3cd9-55e9-4337-919e-0173d94dc34d/FireShot_Capture_051_-__hJ4Wttl.dev.flatlogic.app.png b/media/documents/205a3cd9-55e9-4337-919e-0173d94dc34d/FireShot_Capture_051_-__hJ4Wttl.dev.flatlogic.app.png new file mode 100644 index 0000000..c71c855 Binary files /dev/null and b/media/documents/205a3cd9-55e9-4337-919e-0173d94dc34d/FireShot_Capture_051_-__hJ4Wttl.dev.flatlogic.app.png differ diff --git a/static/css/custom.css b/static/css/custom.css new file mode 100644 index 0000000..bf403a6 --- /dev/null +++ b/static/css/custom.css @@ -0,0 +1,31 @@ +/* Your custom CSS goes here */ + +:root { + --bs-primary: #2196F3; + --bs-primary-rgb: 33, 150, 243; +} + +body { + background-color: #F8F9FA; +} + +.btn-primary { + --bs-btn-bg: #2196F3; + --bs-btn-border-color: #2196F3; + --bs-btn-hover-bg: #1a78c2; + --bs-btn-hover-border-color: #1a78c2; +} + +.kpi-card .card-body { + text-align: center; +} + +.kpi-card h5 { + font-size: 1.1rem; + color: #6c757d; +} + +.kpi-card .display-4 { + font-weight: 700; + color: #343A40; +}