From 5b190bc3f3c41abe30c33d58e3f13f1931fe2292 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 17 Dec 2025 19:53:37 +0000 Subject: [PATCH] clients --- cookies.txt | 5 + core/__pycache__/admin.cpython-311.pyc | Bin 212 -> 1160 bytes core/__pycache__/forms.cpython-311.pyc | Bin 1256 -> 1993 bytes core/__pycache__/models.cpython-311.pyc | Bin 1265 -> 1998 bytes core/__pycache__/urls.cpython-311.pyc | Bin 1201 -> 1378 bytes core/__pycache__/views.cpython-311.pyc | Bin 4547 -> 5783 bytes core/admin.py | 13 ++- core/forms.py | 14 ++- ...ct_end_date_alter_project_name_and_more.py | 93 +++++++++++++++ ...lter_project_name_and_more.cpython-311.pyc | Bin 0 -> 3885 bytes core/models.py | 24 ++-- core/templates/base.html | 2 +- core/templates/base_public.html | 2 + core/templates/core/add_client.html | 48 ++++++++ core/templates/core/add_project.html | 74 +++++++++--- core/templates/core/clients.html | 45 ++++++++ core/templates/core/edit_project.html | 6 +- core/templates/core/index.html | 14 +-- core/templates/core/login.html | 51 +++++++-- core/templates/core/projects.html | 16 +-- core/templates/core/register.html | 59 ++++++++-- core/urls.py | 4 +- core/views.py | 35 ++++-- static/css/custom.css | 106 ++++++++++++++++++ staticfiles/css/custom.css | 106 ++++++++++++++++++ 25 files changed, 650 insertions(+), 67 deletions(-) create mode 100644 cookies.txt create mode 100644 core/migrations/0002_client_alter_project_end_date_alter_project_name_and_more.py create mode 100644 core/migrations/__pycache__/0002_client_alter_project_end_date_alter_project_name_and_more.cpython-311.pyc create mode 100644 core/templates/core/add_client.html create mode 100644 core/templates/core/clients.html 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 cd6f855b12f4883b1ba9de01c54245c53aacd714..0b8cbdb11e4b9c516f3843924407c63bb5f69bf6 100644 GIT binary patch literal 1160 zcma)5&rj4q6rP#>*s|;bvLIk0Q3xcB!k)Yt6T`t6g_wBjr74{Oiv6)p+bFj^@CR_? zKfoCN3=_{_IBe!b;)&Z5IC1j5c1uL|;B@Ben|bejee=!qTeDdw(4J{)!7ob4Z=6)k z92i$W!8jzGa2k?`I+PO5xE8WVbF@fzbV?Wzbl0d7g?m#v8F7O*RRffl-AilsFZKi4;WU?##Oqpw8 zAmYrwtpto}A6E}ya!3SmC?^i%)X_L|bglufu13qu*GOw?N{_urC__YE5Gtdylf=TN zas(JQY(rVDi+91)$(-q+N(j|bu<#`M8OfGld^`@S8*L#naPV||wmkgM^VUMUWzU#%? z$*P~knG7~1-f(5Y8H+HL_=`yV1C%AjHk_}Js=kiCLZkA4iZbq&c4NIPZ^2$eI?!&>0v)1AzI<66rW$u1_q23?PQ4SaY delta 147 zcmeC+yuz5WoR^o20SLbD`juf1q#uJgFu(+5d=>&SrZc24q%h_%L6A~d&Ipo+sV=eh$jf8~6sW(T?DJS0BwK0hu3KFxkKh3;(^LuaJyf?p` zKc6PhK1{AQep3kf11FQFjRzwa29F6PRB}jDG9`&nnJSLlR7|C*nyN%(vQDV_J)s(t zwkG??E-*fYKW?TTY3ZPq_y?^7P2MKO)N|Zal!SKA<4ym$gfm$rZgFpm)dQigIt}Iq z^_V(Fbvzlp1%t|Fk}5J|(lxMjf-pJO91UPFC1i@J zZbC4Al1xn|Oy5e3^`fveq+iutaQ7*?9JNSi!i75889J%S4ysU{CU)exO#e>n1@@@4 zqnYVA)W-{d3qa{63+$pMRM&1Yq1U4@i?kouJg}%8Fp*>~jYm32gSIcUx?}tP@j1Mz z>vhi!xaaJ{)!9M7{ckgo*k=yjn~D@gnJ>Umg&I6+v05=Bl9q+$EK8&s(_VPlz7mfn=J76$q1u+ ze7$_%YqE0ttF{}o%j_Ymw*!xtA9(z0zh&21x$bdRj*_m_dL&c~2ge#g`~J@W-~IIs z7A|dTgNy57)VKBD7neHK?k9Wi9n`|NSHs0hZ?V!}tPHYOJFM&MEg!re7C#EJE4}PW zKf5x>7oQY%%FjySryJqBUtrIhy}a4an}gi)6K&_hvzNkKpNFNp*mE1b+(tjQaR%8i zqrZ|K5`gUle*6k}%C7>{_Enz6$t8r`M9eSa=nBFc2$(MsU={f{0H)_0$|()VXW*n@3}0h1AY(!t`UyOtB2 zTvh0x|ADhx1*c4I+Re$q$-5MXKJFgx^Z33mckeavrNSz13DRtu0^*pCr zfB}X!a9{;OfF&&3k|S4S2qaJdSh)lk5!g_~q=GTewz(fcl|= zs{t`3n&9rC5}Y+jI~FN@Vv}0HE~MRYJEy-J{#cOioJsv!} zz2%)=+ImY{?`eyj%ZF>T+>vH^DwztM5HKlH>c34hqKZ^=qSB(ol1i}>H3gSUHB@n< z>ef9f_7kd(hl%Y|BtYL2VDWzqhKCJ*M>e>)Ws^<1z|~@X{t;W%GRc`97&`1&%f*2K Kjc<{nC;b6ERB|`~ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 9b59ba3a6115bfcd07b201060b7c863c71ea5101..acdcf23a489651fcaa2b9899b94027e50b945c97 100644 GIT binary patch literal 1998 zcma)7y-y=Y6rWvxd)F94!rg_B`$k~I;|SVd-6F3zEb zn&vO2*K%ccqKbGq_rkQ8U8w8`eUI@i$7QAsHW6AX=Ze-wI>@6C$728@v}NV&=OSoV zE$iYvxwF3G*etKWd@yj1-R%tn9Fmu$G_a5?IZ@V56i9$?ho{S|Z5IQk-^|SJIAu0l z-KyG^>MVP~^3{sNXMb?`b9XnFXR~>Svsu6GGrRjDVw!G+n`UjuKhaEojn(#$Ep^gK zjvnlL(fd6tU@aXNQB$7HG)3GrAyw64FxO4<`)ba@noN^p=^~cd$?@ePvnU_IbzC+> zZio=W#)!}^o}7*n9G=ryDKZXzNCOsiRMzZQ@uxyLTS+vfp_6W=r2}>znnZZcqt5 z=#snOmxGnOMNn)Fj@};tHv}yJ;eJt22P{|`s0nmK1I^$qqK2hUL}meCQHVfh$N^Lg z(+G_;qenpH(`z~#aT%pu`^B2^BlyuzuUTX0w>-2v9*JAk=`27Kbf}rUC6OUI4Af83 z6i|OeMAo@eV);rskXA)>7uahT?ShD|_h+!H6>W2uI|a^Mx28R|ry)cxz!u9p)>wJI|5|7($>KzRyb z6u{S^PhwG0h+zkOmBn3biAiBYM43(fSr~Sq15$N-8Z+Mr z??YT7U`yTK0d9oXRfA*UE*U_5t*a9dR$q%#p0?W3R@>StXfZL>c-l@({j~Rb?6;}& zl2>rNy;_%$jJl%2V`x>qlz1hvzT8PY`f;qCdUX8KOU<`Z^X=4p{fkcWVPmhIe0Xel z$+=c?uAQ8#uXYlXn0oSf{)>T%gogx)1k0jcxT~b~W$8uQ@ z1BYHL!GkwXRapg({s9U*NeW?AS>t(Q}@=iUp=ZHI}5l)@_Ypm=$L<)qgLa6^^c1A1vh z9DMubO2xELrE&@znC?|(ga+;sbABpFl-9Cq4@#9>{_b+_b_s<@0-viww5X|d1(7M- zAK`21b9nQSLSy)WMoA<8q2I_W<|p@hWV+_fEPFzsD-?X8&|K(&)b2wcq&}SaGV6hS z7vz1AZ!Y$dnb)I!GUHvD_ma78GUq3A%|cI{*nRGc6W*m;o|x^5SzpXH^CuRkKR?-j z>^(C)(CC7O4;t>zNQNUb6eZS;a;eElGSA6)f1IWI340}w=WIA@wMGcY^`abSQC%J|$hQC(jmmothpmn({kks*a4g(ZhO zmnVuRmp6)cVwx>8XA1Yk1ES14sazBHNwB1{r0@f|tOBX5DS|+TvJewPDo+;7IEEDA zH6qIx85mXrF$AQD0+q>&0hRG0lr00wt%j*!ND&7aDUr&SA_-&|OQo`>NUvdE26Xdk zAclZ+t|a(-S( zQD%}}VrfZ+URh>pd2tahP`-$7vLtgPmkf{zazb(9t<8 delta 360 zcmaFFwUJYOIWI340}!nI@07WdiGkrUhyw#WP{wD!iR$_Sj0`CZDJ(ghxm;0Px!h6Q z6Z32tSthQLv}8@;UBkYNnSo(75JLbHLn>nmACTnAf*Hb)!oNmf86!{?5JSMkU((Fn zSv->i7{!x?u^J(g%9bLERYok8Jw+T{CY>XSH$@_tK~wT2$OcWuTPy{MB^fV4Hfpj> zKE>!ZS(V9has$&QM!w0$%;u9zm}S_wfs#c$lczC9a!CQ1AhpH1lYcVH=!IWkAOTJG YXYrW4pJfWaASXXl12+g3NdOH401iS+p#T5? diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index bfef9f06ee9a492abcf4a6f7f072717df4edcf1f..cbb0ef3a719584c270b9e305a9583146e170fa09 100644 GIT binary patch delta 2247 zcmb7FU1%Fe5WYJ}ccGyB7>*xBN=G%m4>!Yg9NKOGnkOb5q< zuy)Qfx-2oyQ*1pf{NdrX!TXnoqqo>F z`v?7!?V|s(e)=2RL|Z*R8sM7f0`KSN*aG{RKy}w1evX5w2-AiJ#rB1L85};?bo$iB zwq@gZnT>tRhJUe5an4a-3tYiguooPPu;`fMH5mi9MuAbp0>2CyJisi>afW%$!nw?W z9^R+=xNEG+r%axnxV!UOl4ui3Qd6tqOeUGlrn8fAw#tKCyA;Y--RbPa49QIrHJ`6K zQ@QDxjH;;${mM4Sy67ABnB>c=8Q^|`s2T90(DU{Kjk{no(u@#7Xh8_mCHv`OCrq92 z=4$|#nTp_>FBrn!8WV6F+>l#tC)VZOHMw`CQ7t*e-3f5{Vlo5|Z8n(75%mC3 zC)0UNB|THxbjA!RE)p{-5iL#gt)%dRZOhEI#U+V=>e*Ptr@qMm=nkX)3O(=r*b0aH zoiXp|*_Yi>y5i|9Y7%@_A8NT>9x$ta}5YrjLFueImfbAu3|DPpKRjG@QgOe0X9}5lKcNQZfz{p{% zLpiWRwajsv)g{4{VSol`UIk9!7NN`eqD9*dQtV^10rPaNwqP*-|((luF(WsqyAyIr{q7LV0|$d~gaLT}m5Lx-6!Dsq1v9&yf1c zVjnTNTm9MLV~bX$;?}bbX6ksIt^Br?wqTVu7S;2my?I~BYoL9mHMq8Xks(mNj|MXU zmzhV<^+S}O|CvJl;nLJP6QE?tTlQmkaJxTPBd*9NX8t|w{?=ZsZ6{u?K*owz!B zeY7;XA%_=E=yHc4ca%LHH3#Vb78!rb)xz~c$-dzZ+>DjOy(=RR-&r}LyN?;}V`cj> zGvl#%xawCfCbN^dp8QmfXsJsYy)WIHB6w_7$A|E@n74Y8nJC_N(u;5ip&tQnq*cv> zIK!;&Gp2X55j_##p3W(kGU^e6Cy9TD`6OV?#7%Q6y90ykG-}xPBxpy*>0LrC(brMD72~&f~1Ae9~GsQDuIZEqE%*FO#PFN zx1gFeK`268qX(i0r1lhqnnOiyJ+_A)8(S7GmU`)d3lhq?C+6+uha_UO^Lci@_rCYu z%v=9B@#KX5YdWn7e9Q~)6+YBf^?vebHUH5`B2b0Ki(RG6e1@+RE5dxYr&4W2SmVYI z&?6l4J*y8*$KX`z5Yi=xky*H`gr9)fqgQt2%sgr=*SKyYIhb7U}sJI?mBRmZP?}&qFDb{0968kkO9cHA6 zkq%&uCl|4p_w3M9%ZM!#jz&SILR0 z?=pXZy1v&*EEU~yp^Mkt!4f|ZnKG(J{H0=uw`%=rkw2@U@LUQn> zI;NyCVjk92JMW0>IKGWyRIGSth@Id+ouo_Y!cughY#g@KIvL(5$FGo{Jg0fp@j{u` zU0(E94xS|j$te7j$bRB*l12KsI*BlbAR*w9J5m_2qiu^X6eycQGE4!@hWLn*NO`p2 z?`CldsCJJGgOvP1nM98q+)Eyt2=jjf-%+NDF?I!X>J>fT+m$#2eW)X z8H-_f?6sc73iDoP-i<=l_t@m8eyJFy+d1*AYg?mo6G9coT_j&*Uh?FzP!wHj3r z_F9&+jvA)KEWTzbgrf*H!XQF;H{0ESUjOX|7Rn&Je+H?KIeaZVSM`;krRKH-Nk09; z82EmsZTxS`4rj*IVeU5P909xc_sbpmdWDs$QLo?yuyKS_2pA(gq%iJp0z=2JBVNVd uE>-C5qBqUn<$L_!La2_wr1|-4)~2xUc`giY?th?Zey0mEIlYAtYW@p~$u$W8 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 0000000000000000000000000000000000000000..65e0b6056733f38ddc704890ffe32f43624784a3 GIT binary patch literal 3885 zcmb7HO>7&-72aJg|0zWxCDEizS&?PMbQ;lZ(gL;W1deREX=TcYW1|sTZ&#cVMVVYO zvr9V`BtVB86vKdh2oM#>K`08fg?P}hN24bndRPVvWHCU206qCe!#Wf>^}Sh=)?eLv zsD1lp=FNNGzV~L{=+8Yp5eeFuZ@ToG9!dHab~?@9W?oze=4**aL@rAed0wt4^NNgp zrRpLXtTUC}pzt^_9 zIyxix-iBwVJ8)V0U4Biqx?d?tt13~6bnt7}{HuQU^HtwI-D+3GK4#V5@Na|toi2vo z0*1A~T5vV6s%o8BfQ>Em$B}jrn=Cz?BNBKykmqWl$|w(BXVqm|uy|m)T%v}>qXqHS zELy2$Wv&`}g=T|1T%?vJqQHaI1zcfrpI$EWAWZ0%#dr@&RGC&_RJSM(czU>M>UU{2 z#8tgkGkK3$SfUkOqoib28AB(r5D*@$wQ+Rpqg~b~raXc|L?eJ%J2;F8G#%zD zplPyppC>(Sw5`R|7OU)@&ImgJz|i~VAAqb&Zg2dN@xa);we@MUH|O-`)~~uf{f}-x zxV<^E^~+|@J5J9#>oaa5{nZEGB__U2Of(Z`oy6Ie5Z!o^KLBhX!gPE zm-&bJjr_k`(T>&^08TovgD~U74*EMVuepO>e^*Sd`5@oB`<w7_T?FdL8MhS$fC%{kiZcpiDY7Utg$eXejr(u#gR`S5y?0JoQYU4&sY)|e&TT0 z7xcm%5%*!3#uPPmAnVfi$He_;kbM|rAR9q98h`kHOHv@JW7$7VI%7ZGxa!1C9g1JD zvw)?*N&%j3L+sdI`~%K}uO%wY%S4)2h+LHCeTqcY53VrEGsUWA4o<{ExeQzAnr;^~}5R2%Vw zc3`EtENT%NAqg@-1{*^M&r%_S!4?f=gguPBH{Sh=L_V3}%14*8Dv#+<0NT}u@Mw*dDmuHb-J$n+*rW{BlvXH@ z-KA`yYEn(q7fh^YPl2pNz4AAj$8v|45B+nkOpT(o#KvKs`=M%;%G`eiUtrC@P}Yq* zYy#(0qh2nz#cE4cgFeH+bN_YZ0XXg8h&=rG>UIsl`S?E}awJ!+5GdyBiIjU3~YTz29WE6Sm zdYgmMXvx5WTV&KUSE3&oqo7xGU@ly!R%-YWgomd)EV}@Rc>s){VwDFAOVv_=nmkD8 zqFygs!ubMZK6`?PG~q5y;}H!nD)ll2KBj4()%CI{<s0@zIYd>hwCHON6vVW*}CjqVa1;x&T?$jbfE6 zWPyi7jNmHEgVUF8OkcWug@-?`8`tk!kRsyxz_6BZP+O_qZL5u3!dr@`IE*1_+o;xP z`(FH4Nz?>UrdS3P2!gi}Q-_MM-u<-#Quxi5PZrbI3_$cW*pdvr{*lshdvfrrmeT?LT3s z-`<>X(r@pMKY91*C-(1)c6zCqUUJe)c3(-%pTqfcy8}uKQB(k0Fk+-H08ZYR0FW_|#$KkpnA| zofUs6Jn6OLbItgi6Q6tanoMWSZp@BfY{oA-@r$os0sirDwIu0BA1a6Fta1y73JS%S zrL5vBv8Fs1bWhO3`s?xs@xc18@aJV8CLUby!(P5mk_EA|yv>yA4o-?=&YbM*1a=Mc z6?0XjzL&nYu#49mk 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 %} +
      +
      +
      +
      +
      +

      Create a New Client

      +
      +
      +
      + {% csrf_token %} +
      + {{ form.name }} + +
      +
      + {{ form.email }} + +
      +
      + {{ form.phone }} + +
      +
      + Cancel + +
      +
      +
      +
      +
      +
      +
      + +{% 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

      -
      - -
      -
      -
      -
      - {% csrf_token %} - {{ form.as_p }} - -
      +
      +
      +
      +
      +
      +

      Create a New Project

      +
      +
      +
      + {% csrf_token %} +
      +
      +
      + {{ form.name }} + +
      +
      +
      +
      + {{ form.client }} + +
      +
      +
      +
      +
      +
      + {{ form.start_date }} + +
      +
      +
      +
      + {{ form.end_date }} + +
      +
      +
      +
      + {{ form.status }} + +
      +
      + Cancel + +
      +
      +
      +
      +
      -
      + {% 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

      +
      +
      + + Add client +
      +
      + +
      + {% if clients %} + {% for client in clients %} +
      +
      +

      {{ client.name }}

      +
      +
      +

      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 @@
      {% csrf_token %} {{ form.as_p }} - +
      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/' %}
      -

      Wszystkie projekty

      +

      All 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

      -
      - {% csrf_token %} - {{ form.as_p }} - -
      +
      +
      +
      +
      +

      webFirma

      +

      Streamline your client and project management.

      +
      +
      +
      +
      +
      +

      Login

      +
      + {% csrf_token %} +
      + {{ form.username }} + +
      +
      + {{ form.password }} + +
      +
      + +
      +
      +
      +

      Don't have an account? Register here

      +
      +
      +
      +
      +
      + + {% 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

      {% if user.is_authenticated %} - Dodaj projekt + Add project {% endif %}
      @@ -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

      -
      - {% csrf_token %} - {{ form.as_p }} - -
      +
      +
      +
      +
      +

      webFirma

      +

      Join us and streamline your workflow.

      +
      +
      +
      +
      +
      +

      Create Account

      +
      + {% csrf_token %} +
      + {{ form.username }} + +
      +
      + {{ form.email }} + +
      +
      + {{ form.password }} + +
      +
      + {{ form.password2 }} + +
      +
      + +
      +
      +
      +

      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; +}