diff --git a/cookies.txt b/cookies.txt
new file mode 100644
index 0000000..0b8b044
--- /dev/null
+++ b/cookies.txt
@@ -0,0 +1,5 @@
+# Netscape HTTP Cookie File
+# https://curl.se/docs/http-cookies.html
+# This file was generated by libcurl! Edit at your own risk.
+
+127.0.0.1 FALSE / TRUE 1797450401 csrftoken gNby2S5EHlDB1Xsw0eGC7JVKq4F45wQW
diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc
index cd6f855..0b8cbdb 100644
Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ
diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc
index 054082f..0864d42 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 9b59ba3..acdcf23 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 4c8f574..57df135 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 bfef9f0..cbb0ef3 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 8c38f3f..4ff6fd6 100644
--- a/core/admin.py
+++ b/core/admin.py
@@ -1,3 +1,14 @@
from django.contrib import admin
+from .models import Project, Client
-# Register your models here.
+@admin.register(Client)
+class ClientAdmin(admin.ModelAdmin):
+ list_display = ('name', 'email', 'phone')
+ search_fields = ('name',)
+
+@admin.register(Project)
+class ProjectAdmin(admin.ModelAdmin):
+ list_display = ('name', 'client', 'start_date', 'end_date', 'status')
+ list_filter = ('status', 'client')
+ search_fields = ('name', 'client__name')
+ autocomplete_fields = ('client',)
\ No newline at end of file
diff --git a/core/forms.py b/core/forms.py
index 81f090e..f917e0d 100644
--- a/core/forms.py
+++ b/core/forms.py
@@ -1,5 +1,5 @@
from django import forms
-from .models import Project
+from .models import Project, Client
class ProjectForm(forms.ModelForm):
class Meta:
@@ -7,8 +7,18 @@ class ProjectForm(forms.ModelForm):
fields = ['name', 'client', 'start_date', 'end_date', 'status']
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
- 'client': forms.TextInput(attrs={'class': 'form-control'}),
+ 'client': forms.Select(attrs={'class': 'form-select'}),
'start_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'end_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'status': forms.Select(attrs={'class': 'form-select'}),
}
+
+class ClientForm(forms.ModelForm):
+ class Meta:
+ model = Client
+ fields = ['name', 'email', 'phone']
+ widgets = {
+ 'name': forms.TextInput(attrs={'class': 'form-control'}),
+ 'email': forms.EmailInput(attrs={'class': 'form-control'}),
+ 'phone': forms.TextInput(attrs={'class': 'form-control'}),
+ }
\ No newline at end of file
diff --git a/core/migrations/0002_client_alter_project_end_date_alter_project_name_and_more.py b/core/migrations/0002_client_alter_project_end_date_alter_project_name_and_more.py
new file mode 100644
index 0000000..9f9af2c
--- /dev/null
+++ b/core/migrations/0002_client_alter_project_end_date_alter_project_name_and_more.py
@@ -0,0 +1,93 @@
+# Generated by Django 5.2.7 on 2025-12-17 19:22
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+def migrate_clients_forward(apps, schema_editor):
+ Project = apps.get_model('core', 'Project')
+ Client = apps.get_model('core', 'Client')
+ for project in Project.objects.all():
+ client_name = getattr(project, 'client_temp', None)
+ if client_name:
+ client, created = Client.objects.get_or_create(name=client_name)
+ project.client = client
+ project.save()
+
+def migrate_clients_backward(apps, schema_editor):
+ # This is a one-way migration, but we can try to restore the old values
+ Project = apps.get_model('core', 'Project')
+ for project in Project.objects.all():
+ if project.client:
+ project.client_temp = project.client.name
+ project.save()
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0001_initial'),
+ ]
+
+ operations = [
+ # 1. Create the new Client model
+ migrations.CreateModel(
+ name='Client',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=200, verbose_name='Client Name')),
+ ('email', models.EmailField(blank=True, max_length=200, null=True, verbose_name='Email')),
+ ('phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='Phone')),
+ ],
+ ),
+
+ # 2. Rename the existing 'client' CharField to a temporary field
+ migrations.RenameField(
+ model_name='project',
+ old_name='client',
+ new_name='client_temp',
+ ),
+
+ # 3. Add the new 'client' ForeignKey field, allowing it to be null for now
+ migrations.AddField(
+ model_name='project',
+ name='client',
+ field=models.ForeignKey(
+ to='core.Client',
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='projects',
+ verbose_name='Client',
+ null=True, # Allow null during migration
+ blank=True
+ ),
+ ),
+
+ # 4. Run the Python script to migrate the data
+ migrations.RunPython(migrate_clients_forward, migrate_clients_backward),
+
+ # 5. Remove the temporary CharField
+ migrations.RemoveField(
+ model_name='project',
+ name='client_temp',
+ ),
+
+ # 6. Alter other fields (translations)
+ migrations.AlterField(
+ model_name='project',
+ name='end_date',
+ field=models.DateField(verbose_name='End Date'),
+ ),
+ migrations.AlterField(
+ model_name='project',
+ name='name',
+ field=models.CharField(max_length=200, verbose_name='Project Name'),
+ ),
+ migrations.AlterField(
+ model_name='project',
+ name='start_date',
+ field=models.DateField(verbose_name='Start Date'),
+ ),
+ migrations.AlterField(
+ model_name='project',
+ name='status',
+ field=models.CharField(choices=[('planning', 'Planning'), ('in_progress', 'In Progress'), ('completed', 'Completed')], default='planning', max_length=20, verbose_name='Status'),
+ ),
+ ]
\ No newline at end of file
diff --git a/core/migrations/__pycache__/0002_client_alter_project_end_date_alter_project_name_and_more.cpython-311.pyc b/core/migrations/__pycache__/0002_client_alter_project_end_date_alter_project_name_and_more.cpython-311.pyc
new file mode 100644
index 0000000..65e0b60
Binary files /dev/null and b/core/migrations/__pycache__/0002_client_alter_project_end_date_alter_project_name_and_more.cpython-311.pyc differ
diff --git a/core/models.py b/core/models.py
index 1aa45a7..4e3a136 100644
--- a/core/models.py
+++ b/core/models.py
@@ -1,17 +1,25 @@
from django.db import models
+class Client(models.Model):
+ name = models.CharField(max_length=200, verbose_name="Client Name")
+ email = models.EmailField(max_length=200, verbose_name="Email", blank=True, null=True)
+ phone = models.CharField(max_length=20, verbose_name="Phone", blank=True, null=True)
+
+ def __str__(self):
+ return self.name
+
class Project(models.Model):
STATUS_CHOICES = [
- ('planowany', 'Planowany'),
- ('realizowany', 'Realizowany'),
- ('zakonczony', 'Zakończony'),
+ ('planning', 'Planning'),
+ ('in_progress', 'In Progress'),
+ ('completed', 'Completed'),
]
- name = models.CharField(max_length=200, verbose_name="Nazwa projektu")
- client = models.CharField(max_length=200, verbose_name="Kontrahent")
- start_date = models.DateField(verbose_name="Data rozpoczęcia")
- end_date = models.DateField(verbose_name="Data zakończenia")
- status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='planowany', verbose_name="Status")
+ name = models.CharField(max_length=200, verbose_name="Project Name")
+ client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name='projects', verbose_name="Client", null=True, blank=True)
+ start_date = models.DateField(verbose_name="Start Date")
+ end_date = models.DateField(verbose_name="End Date")
+ status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='planning', verbose_name="Status")
def __str__(self):
return self.name
\ No newline at end of file
diff --git a/core/templates/base.html b/core/templates/base.html
index 19b1e50..35df213 100644
--- a/core/templates/base.html
+++ b/core/templates/base.html
@@ -33,7 +33,7 @@
Dashboard
Projects
Invoices
- Clients
+ Clients
Settings
diff --git a/core/templates/base_public.html b/core/templates/base_public.html
index 36ea136..47d5e0c 100644
--- a/core/templates/base_public.html
+++ b/core/templates/base_public.html
@@ -14,6 +14,7 @@
{% endif %}
{% load static %}
+
@@ -37,6 +38,7 @@
{% block content %}{% endblock %}
+
diff --git a/core/templates/core/add_client.html b/core/templates/core/add_client.html
new file mode 100644
index 0000000..8b591ac
--- /dev/null
+++ b/core/templates/core/add_client.html
@@ -0,0 +1,48 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block title %}Add Client - webFirma{% endblock %}
+
+{% block content %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/core/templates/core/add_project.html b/core/templates/core/add_project.html
index 2342664..aae1338 100644
--- a/core/templates/core/add_project.html
+++ b/core/templates/core/add_project.html
@@ -1,22 +1,68 @@
{% extends 'base.html' %}
{% load static %}
-{% block title %}Dodaj Projekt - webFirma{% endblock %}
+{% block title %}Add Project - webFirma{% endblock %}
{% block content %}
-
-
Dodaj Nowy Projekt
-
-
-
-
-
+
{% endblock %}
\ No newline at end of file
diff --git a/core/templates/core/clients.html b/core/templates/core/clients.html
new file mode 100644
index 0000000..91bbd3b
--- /dev/null
+++ b/core/templates/core/clients.html
@@ -0,0 +1,45 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block title %}Clients - webFirma{% endblock %}
+
+{% block content %}
+
+
+
Clients
+
You have {{ total_clients }} clients
+
+
+
+
+
+ {% if clients %}
+ {% for client in clients %}
+
+
+
+
Email: {{ client.email }}
+
Phone: {{ client.phone_number }}
+
+
+ {% endfor %}
+ {% else %}
+
+
+
+
+
No clients yet
+
You don't have any clients yet. Click the button to add your first client.
+
Add client
+
+ {% endif %}
+
+{% endblock %}
diff --git a/core/templates/core/edit_project.html b/core/templates/core/edit_project.html
index b874bee..7ec6fba 100644
--- a/core/templates/core/edit_project.html
+++ b/core/templates/core/edit_project.html
@@ -1,11 +1,11 @@
{% extends 'base.html' %}
{% load static %}
-{% block title %}Edytuj Projekt - webFirma{% endblock %}
+{% block title %}Edit Project - webFirma{% endblock %}
{% block content %}
-
Edytuj Projekt
+ Edit Project
@@ -14,7 +14,7 @@
diff --git a/core/templates/core/index.html b/core/templates/core/index.html
index d99891d..0d27535 100644
--- a/core/templates/core/index.html
+++ b/core/templates/core/index.html
@@ -12,7 +12,7 @@
{% if user.is_authenticated %}
- Dodaj projekt
+ Add project
{% endif %}
@@ -59,7 +59,7 @@
{% if request.path == '/projects/' %}
{% endif %}
{% if projects %}
@@ -70,8 +70,8 @@
{{ project.get_status_display }}
-
Klient: {{ project.client }}
-
Okres: {{ project.start_date|date:"d M Y" }} - {{ project.end_date|date:"d M Y" }}
+
Client: {{ project.client.name }}
+
Period: {{ project.start_date|date:"d M Y" }} - {{ project.end_date|date:"d M Y" }}
{% endif %}
diff --git a/core/templates/core/login.html b/core/templates/core/login.html
index 4ea024b..613fc5b 100644
--- a/core/templates/core/login.html
+++ b/core/templates/core/login.html
@@ -1,14 +1,51 @@
{% extends 'base_public.html' %}
+{% load static %}
{% block title %}Login - webFirma{% endblock %}
{% block content %}
-
-
Login
-
+
+
+
+
+
webFirma
+
Streamline your client and project management.
+
+
+
+
+
+
{% endblock %}
diff --git a/core/templates/core/projects.html b/core/templates/core/projects.html
index e55aaee..750955d 100644
--- a/core/templates/core/projects.html
+++ b/core/templates/core/projects.html
@@ -1,18 +1,18 @@
{% extends 'base.html' %}
{% load static %}
-{% block title %}Projekty - webFirma{% endblock %}
+{% block title %}Projects - webFirma{% endblock %}
{% block content %}
-
Projekty
+ Projects
@@ -26,8 +26,8 @@
{{ project.get_status_display }}
-
Klient: {{ project.client }}
-
Okres: {{ project.start_date|date:"d M Y" }} - {{ project.end_date|date:"d M Y" }}
+
Client: {{ project.client.name }}
+
Period: {{ project.start_date|date:"d M Y" }} - {{ project.end_date|date:"d M Y" }}
{% endif %}
diff --git a/core/templates/core/register.html b/core/templates/core/register.html
index b5985e5..9b6df3a 100644
--- a/core/templates/core/register.html
+++ b/core/templates/core/register.html
@@ -1,14 +1,59 @@
{% extends 'base_public.html' %}
+{% load static %}
{% block title %}Register - webFirma{% endblock %}
{% block content %}
-
-
Register
-
+
+
+
+
+
webFirma
+
Join us and streamline your workflow.
+
+
+
+
+
+
Create Account
+
+
+
Already have an account? Login here
+
+
+
+
+
+
+
{% endblock %}
diff --git a/core/urls.py b/core/urls.py
index 70b96c9..7c3ac0a 100644
--- a/core/urls.py
+++ b/core/urls.py
@@ -1,10 +1,12 @@
from django.urls import path
-from .views import index, add_project, register, edit_project, delete_project, projects
+from .views import index, add_project, register, edit_project, delete_project, projects, clients, add_client
from django.contrib.auth.views import LoginView, LogoutView
urlpatterns = [
path('', index, name='index'),
path('projects/', projects, name='projects'),
+ path('clients/', clients, name='clients'),
+ path('clients/add/', add_client, name='add_client'),
path('add-project/', add_project, name='add_project'),
path('register/', register, name='register'),
path('login/', LoginView.as_view(template_name='core/login.html'), name='login'),
diff --git a/core/views.py b/core/views.py
index a78d3c6..aa2a090 100644
--- a/core/views.py
+++ b/core/views.py
@@ -3,17 +3,17 @@ from django.contrib.auth import login
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.views import LoginView, LogoutView
-from .models import Project
-from .forms import ProjectForm
+from .models import Project, Client
+from .forms import ProjectForm, ClientForm
def index(request):
if request.user.is_authenticated:
- projects = Project.objects.all().order_by('-start_date')
+ projects = Project.objects.select_related("client").all().order_by('-start_date')
total_projects = projects.count()
- planned_projects = projects.filter(status='planowany').count()
- in_progress_projects = projects.filter(status='realizowany').count()
- completed_projects = projects.filter(status='zakonczony').count()
+ planned_projects = projects.filter(status='planning').count()
+ in_progress_projects = projects.filter(status='in_progress').count()
+ completed_projects = projects.filter(status='completed').count()
context = {
'projects': projects,
@@ -68,7 +68,26 @@ def register(request):
def projects(request):
if request.user.is_authenticated:
- projects = Project.objects.all().order_by('-start_date')
+ projects = Project.objects.select_related("client").all().order_by('-start_date')
return render(request, 'core/projects.html', {'projects': projects})
else:
- return render(request, 'core/landing.html')
\ No newline at end of file
+ return render(request, 'core/landing.html')
+
+def clients(request):
+ if request.user.is_authenticated:
+ clients = Client.objects.all()
+ total_clients = clients.count()
+ return render(request, "core/clients.html", {"clients": clients, "total_clients": total_clients})
+ else:
+ return render(request, "core/landing.html")
+
+@login_required
+def add_client(request):
+ if request.method == 'POST':
+ form = ClientForm(request.POST)
+ if form.is_valid():
+ form.save()
+ return redirect('clients')
+ else:
+ form = ClientForm()
+ return render(request, 'core/add_client.html', {'form': form})
diff --git a/static/css/custom.css b/static/css/custom.css
index b39604b..3d3f3ed 100644
--- a/static/css/custom.css
+++ b/static/css/custom.css
@@ -145,6 +145,21 @@ body {
background-color: var(--primary-dark);
}
+.btn-secondary {
+ background-color: var(--white-color);
+ color: var(--text-color);
+ padding: 10px 16px;
+ border-radius: var(--border-radius);
+ text-decoration: none;
+ font-weight: 600;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ border: 1px solid var(--medium-grey);
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+}
+
/* Project Cards */
.content-grid {
display: grid;
@@ -379,3 +394,94 @@ body {
.stat-card.completed {
border-left-color: #2E7D32;
}
+
+/* Auth Card */
+.auth-card {
+ background-color: var(--white-color);
+ border-radius: var(--border-radius);
+ box-shadow: var(--card-shadow);
+ padding: 48px;
+ width: 100%;
+ max-width: 450px;
+}
+
+.auth-card-header h2 {
+ font-size: 24px;
+ font-weight: 700;
+ color: var(--primary-dark);
+ margin-bottom: 8px;
+}
+
+.auth-card-header p {
+ color: var(--dark-grey);
+ margin-bottom: 32px;
+}
+
+.auth-card form p {
+ margin-bottom: 16px;
+}
+
+.auth-card form label {
+ display: block;
+ margin-bottom: 8px;
+ font-weight: 600;
+}
+
+.auth-card form input {
+ width: 100%;
+ padding: 12px;
+ border-radius: var(--border-radius);
+ border: 1px solid var(--medium-grey);
+ font-size: 14px;
+}
+
+.auth-card form .btn-primary {
+ width: 100%;
+ justify-content: center;
+ padding: 14px;
+ font-size: 16px;
+}
+
+/* Form Card */
+.form-card {
+ background-color: var(--white-color);
+ border-radius: var(--border-radius);
+ box-shadow: var(--card-shadow);
+ padding: 32px;
+}
+
+.form-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 24px;
+}
+
+.form-group {
+ margin-bottom: 16px;
+}
+
+.form-group.full-width {
+ grid-column: 1 / -1;
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 8px;
+ font-weight: 600;
+}
+
+.form-group input,
+.form-group select {
+ width: 100%;
+ padding: 12px;
+ border-radius: var(--border-radius);
+ border: 1px solid var(--medium-grey);
+ font-size: 14px;
+ box-sizing: border-box;
+}
+
+.form-actions {
+ margin-top: 32px;
+ display: flex;
+ gap: 16px;
+}
diff --git a/staticfiles/css/custom.css b/staticfiles/css/custom.css
index b39604b..3d3f3ed 100644
--- a/staticfiles/css/custom.css
+++ b/staticfiles/css/custom.css
@@ -145,6 +145,21 @@ body {
background-color: var(--primary-dark);
}
+.btn-secondary {
+ background-color: var(--white-color);
+ color: var(--text-color);
+ padding: 10px 16px;
+ border-radius: var(--border-radius);
+ text-decoration: none;
+ font-weight: 600;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ border: 1px solid var(--medium-grey);
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+}
+
/* Project Cards */
.content-grid {
display: grid;
@@ -379,3 +394,94 @@ body {
.stat-card.completed {
border-left-color: #2E7D32;
}
+
+/* Auth Card */
+.auth-card {
+ background-color: var(--white-color);
+ border-radius: var(--border-radius);
+ box-shadow: var(--card-shadow);
+ padding: 48px;
+ width: 100%;
+ max-width: 450px;
+}
+
+.auth-card-header h2 {
+ font-size: 24px;
+ font-weight: 700;
+ color: var(--primary-dark);
+ margin-bottom: 8px;
+}
+
+.auth-card-header p {
+ color: var(--dark-grey);
+ margin-bottom: 32px;
+}
+
+.auth-card form p {
+ margin-bottom: 16px;
+}
+
+.auth-card form label {
+ display: block;
+ margin-bottom: 8px;
+ font-weight: 600;
+}
+
+.auth-card form input {
+ width: 100%;
+ padding: 12px;
+ border-radius: var(--border-radius);
+ border: 1px solid var(--medium-grey);
+ font-size: 14px;
+}
+
+.auth-card form .btn-primary {
+ width: 100%;
+ justify-content: center;
+ padding: 14px;
+ font-size: 16px;
+}
+
+/* Form Card */
+.form-card {
+ background-color: var(--white-color);
+ border-radius: var(--border-radius);
+ box-shadow: var(--card-shadow);
+ padding: 32px;
+}
+
+.form-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 24px;
+}
+
+.form-group {
+ margin-bottom: 16px;
+}
+
+.form-group.full-width {
+ grid-column: 1 / -1;
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 8px;
+ font-weight: 600;
+}
+
+.form-group input,
+.form-group select {
+ width: 100%;
+ padding: 12px;
+ border-radius: var(--border-radius);
+ border: 1px solid var(--medium-grey);
+ font-size: 14px;
+ box-sizing: border-box;
+}
+
+.form-actions {
+ margin-top: 32px;
+ display: flex;
+ gap: 16px;
+}