From 994bb04de081bf898af75b24f23eeef062871ce1 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 7 Feb 2026 05:16:18 +0000 Subject: [PATCH] SJP Admin --- core/__pycache__/models.cpython-311.pyc | Bin 209 -> 9784 bytes core/__pycache__/urls.cpython-311.pyc | Bin 347 -> 4096 bytes core/__pycache__/views.cpython-311.pyc | Bin 1364 -> 42340 bytes core/migrations/0001_initial.py | 55 ++ ...nakademik_is_active_matakuliah_and_more.py | 90 +++ .../0003_kelas_prodi_ruangan_prodi.py | 24 + core/migrations/0004_daftarhadir.py | 27 + ...ampu_kelas_dosenpengampu_prodi_and_more.py | 33 + core/migrations/0006_jadwal_is_manual.py | 18 + .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 1796 bytes ...active_matakuliah_and_more.cpython-311.pyc | Bin 0 -> 4002 bytes ..._kelas_prodi_ruangan_prodi.cpython-311.pyc | Bin 0 -> 1222 bytes .../0004_daftarhadir.cpython-311.pyc | Bin 0 -> 1460 bytes ...senpengampu_prodi_and_more.cpython-311.pyc | Bin 0 -> 1533 bytes .../0006_jadwal_is_manual.cpython-311.pyc | Bin 0 -> 833 bytes core/models.py | 121 +++- core/templates/base.html | 185 ++++- core/templates/core/buat_jadwal.html | 31 + core/templates/core/daftar_hadir_list.html | 175 +++++ core/templates/core/dashboard.html | 145 ++++ core/templates/core/dosen_list.html | 162 +++++ core/templates/core/hari_list.html | 26 + core/templates/core/jadwal_list.html | 257 +++++++ core/templates/core/jam_list.html | 168 +++++ core/templates/core/kelas_list.html | 170 +++++ core/templates/core/matkul_list.html | 211 ++++++ core/templates/core/pengampu_list.html | 267 +++++++ core/templates/core/prodi_list.html | 26 + core/templates/core/ruangan_list.html | 182 +++++ core/templates/core/tahun_list.html | 27 + core/urls.py | 55 +- core/views.py | 670 +++++++++++++++++- requirements.txt | 1 + static/css/custom.css | 154 +++- 34 files changed, 3231 insertions(+), 49 deletions(-) create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/0002_jam_kelas_ruangan_tahunakademik_is_active_matakuliah_and_more.py create mode 100644 core/migrations/0003_kelas_prodi_ruangan_prodi.py create mode 100644 core/migrations/0004_daftarhadir.py create mode 100644 core/migrations/0005_dosenpengampu_kelas_dosenpengampu_prodi_and_more.py create mode 100644 core/migrations/0006_jadwal_is_manual.py create mode 100644 core/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0002_jam_kelas_ruangan_tahunakademik_is_active_matakuliah_and_more.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0003_kelas_prodi_ruangan_prodi.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0004_daftarhadir.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0005_dosenpengampu_kelas_dosenpengampu_prodi_and_more.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0006_jadwal_is_manual.cpython-311.pyc create mode 100644 core/templates/core/buat_jadwal.html create mode 100644 core/templates/core/daftar_hadir_list.html create mode 100644 core/templates/core/dashboard.html create mode 100644 core/templates/core/dosen_list.html create mode 100644 core/templates/core/hari_list.html create mode 100644 core/templates/core/jadwal_list.html create mode 100644 core/templates/core/jam_list.html create mode 100644 core/templates/core/kelas_list.html create mode 100644 core/templates/core/matkul_list.html create mode 100644 core/templates/core/pengampu_list.html create mode 100644 core/templates/core/prodi_list.html create mode 100644 core/templates/core/ruangan_list.html create mode 100644 core/templates/core/tahun_list.html diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index e061640edbdcec3e5c1744466c916e7acdd2763b..c7553e04779ba42ef00d6ea8986ddb078c084af2 100644 GIT binary patch literal 9784 zcmdT~No*VE6`tX~3`vQyoJF$5*fL{!kzzNMT~~FyNb;7*ZY;Zn;)EV+#xgC6QvM;m zp$kz2g$_ocgMk=L1LzRAGOU9RKID*t4?&Sb4?}bc2!kF9^yHfj>7-NNdq0QEa7Z~y zdgw^=G&Aqbzkcug-~UE`jYL8M9RK*+A2WZB3c`Qz#yY&^i`A#1Abc(;f+A*xX>nW> z1($G2P+Xr0idz+Lx~vx-xdq_~octJfla&Xoye+IeWaR@Ze+w%wSp~o<*uu(3Rw1wo zx3Kb)l>}Ci7FGeWih@3;$z zKgPX^`!ivj4>e8hBLKjY3JAviiVv#xo6-d~Gs)!Xi}`|*d9J|Qk3};yE#FCJ)!bzM z#zdt^P~kNqXSEH)=fZD%TEGt@anL6>|AP zzj{ZVDCB2Y|LqwzrOnC{YX8IxQ~P=6`)2Q&{&ZT)vvm62mfFVkm7n^c%vJPA0n`Ycrb4EoZX@CTAZD3@c_a5T*wUXItPh2&c9cM6vR- zvVFfE=;FWpoi5OiJ`OOnW3Qoj9R-fN89aGIW+NFjt1t;4;yf^XfxU^MQYS-JAc3!G zS3%TN8Sa`JFv496njYR;3hy<-dy7LaJfioF7eYn&L~kG7+QmzJw&tMVm1Dhi6XAge zc&z3@R42lYU&XcLwjmVAqz>brC9e(RJ_QJAoD+o~TrFTLA)o|dszauC2&O0Z9A{uG z`v*(~QxIk?uY&&ni}BP=LpA8Q#cY3P3gqUOjeb1#C>Kkqow7E zdT_9H&+3XV@~hciAefad$)^A(qM&uN)us-XMvfF@zV7QS{ucPTPp*w8W`pULIbNtMT&JnDV|)3?U=h|#CDWoy+*9JczQ+JHaB8O+a6|@uRqP{(r`%{Hl*R=FuMFc^zpLr zxxB~47lN~VNzd1sX!&?zS0>=B99CShx&V)=z_Gcu!)A^wthWW5YeN7G?i#RaQG>Ov zthgzL@c}EfMzYruAvRTu6Ei31y z>Kqu|lN=bQx!!><3LAyav2ul$dlKL_yTC|RVYJ4ag66bXV@5%Z=xkVH-ga7JQqks4 zLL0N=P?Q;*l4oTtlb1C%1owPXHjG!T{Q#TFP#}P`KY`fD56YoSsco0hwySui9^&7< ztN-dFUAj_|t{Bo4j&s{qZ#Tl;E^ukvjZ2$d!O@KkbVm0dK(l#ttNvOvJK{G;_~rQ; zC;W!X`1PJsvoQDxXb;8SW9$qzy0U`6%@2ZWgvz(TI)c?~7OWv&6hZZuP*4k~j)5Bc z!FHjDqu|UfZ2-#e1ww2DLvaF5?ROwH0%0Amz5?j%TRFsFa)XT?HmDAC9(aJq|0+M` zJ}XbN(@?=?k_0Ky3^QO*HGNy2$`_o|1!1Vcg3x)WNW;tm<+hAij~?iu8LsQIh2Q4M ztpJuuSh@^_wZPIis6WI??OB7JjLTg;s=W7a4}9GBFX~tQN4I;7l@Z$Bq_UsI|f!+H+I${y4nq+*o>r9FHn$W zDJ+S$_?IuZ7V-o&(9)X6)>Z3JOKV6kgT87l)mkeq%9DY%W-*W0-iU7aMojTQ3ZsqU zcSvR^K3o6V6+c9+0h1@PEc%OU{dW})h;J0ZrA~MzQnw>ca;4ctnaxJyVu42>tZV9F}(_A`FMEho0jua1&B|k zXaR8C4Rlg&&FY=yfmfhd?Fb0IA=vYvUyn_|?}GBxl#v|ZH{Xr@eOy0s_3Pem_I%U- zb-(`cRsI}hU}7^48?SE+?Gd8%EMc{N(-ZY%74@J$25O-V=hR~`91qCBl@rq(^ z8Lx2PU`F$py-Leg`U>;9JZhnJ;TqmHjpK0)8H!7AYX1aL1c6yC9-|)vJu(-@ZR#}r=RGNp;BbXhzu1+RyvdOmyOQk!f&7XzK!ag=S!XEjn4DM z(biE;MN1Vu8`3X~>C&Z=bjgq|@fb*f;9m4rBruFUd4u|XWSADI9qeZv!+78U9;*ZY zhYZuebuHPY!8%4B zW4dobv_LDNBjrSSr!20)uV>mjAc$*>_%0*94;DW?_A&e}D^D}9J*Des`ETzSr#|90 zdf?;DFbsqlIG{W#n1o7O@JSBT)7UtBRFHIx3br(78KMGW8SE@=YD_=lyGqK(u_=5` z;XxL!s`Szix0LCfmUHm6tvTIbXBY}(CGFoJYUrjZc|?2Wt{c&w1^@EE)1!LyL@9d0 zh@L2(S_yaK*Rk%0V~dxUKGDO2rSPB;9)$dGs0;H7T@Oz#4lEtjLq|%XBSz>5Bp8}f zTCd7oyIrjO-7Y*NUxzQJr)9{C5fMBGE)j?f@H^gFyg3J= zjv3ICi5Ze`0sjwyDMPs82k-!TI~GF+__&9B{GOj`Ig+>+fL!9(WcCOq6n!_zXH0({ zg7`_mprcoJ)HKs_?ORa1)(rv!`N4-<{?et#2H|&6S<3P&JutW#H*Tw~jKTzoKS@vr zS~&F`Xw6-0mfflMIXbm-8gK{-AYbF51AbO9gF|v&S)`L~G|c0F>e6gg%@jzORh-HtB3 zy%bFt(FAyMM{<6^=twSps&^bHbsR7{4iw*CX@6^etI__}LVo#Oz5Q^h{jkw~xOf(~ z8^1swEDLbS{dj8^=#u-(44TX@K?*b( zZ?Wa(r=;c-`)M-W;!Md(%uCPLOGzqX21>4E_zY6>OHV%|KQ~psG^sSNq*On(A~m_R zB)>?%JijQrxF9h(RX;huC{-U~j9x+IFAf_ZyEG@&u80Guoe_wOWr4&8W=2NF8w@fR Ku%RM0pb7xLi8T)Z diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 5a69659f6c6e0ae848e54157af197c543a09315f..04683a129d0062074bb666b734944416cb5151a3 100644 GIT binary patch literal 4096 zcmZ{m%Tp9b9LHy&c`VDaAn*5c6%ZIf5CwUNphl2@XdYIQWnd-(?y_6GyHWS#Ddiyt zQe{;*l1oA=IdJrze_*Dn57u6D@-|i8JmvSB?rB3!dRS(rf8WpV_v_bm_wUhYl?K-@ zfB(3D1aJRf!N1DI%ZEZ45Spo(p`?}yJqd+0v&1Y-mZVCbl!mmYT8UXu7Qce34{`vKD$fGKNf@u6QD-~22C_lSqRw<|mP!%#(wStNQRVk?&1r-A-EU8)r zRR@&UUr3r46jVJ>5gDsNK{WzZEvY62)eKZrQY{Lq6{s3XwJE4}pkk8hP*9yf)k>;M zL3IOFC#fC<)eF=GN%bkHexT|lHK3s4Ks89}qJp{vRHLK@71R(=O_CZ`P?v#fmeh!X z8U?CFQez5A2dY(4R}|DZP;HW$P*7KaYM0ccf|>%VLsHii)HG0?lDe*-ZUEIKsTl<| z3skqHZYrobpn4=Vub>uy>Xp=@g1QA%pQM%))NP>pCAF-eR)89i)ExzN7pSF>-tFL%dM-!@l)AThxkS)5z}aqz!5! za%iNiLd-ZYUg%G)+~_kaX=L@{1$a+*?}?q4i40Af`-NHwrHtIOd{QsgBgG4!uyT*q zi&fq;k=)Pb3QgQ*T1hKs={NTeb2FJ|^LnwGGu&P%H`7_`5Inu7@Z`3jCuf9bj8i!) zl}Q?qp;*IZ-%yBhU_X^f(;VdGGA|Y?{PsjdTf90q3u_>CR8S&!t>FAzdNC#d-%_X% zh@ak%Ao5qzym9!{fRKr| zi+ZD7jgnt1l;@1SJWeC;DW(y(1%g&{MuaWfGw8lv4C90QHF}nSTf`%TS2$zA=%q4w zy%_ahxp%8y47MibOR`C(@qNyqqkD$APxWFQFDASqkJfpP2-K3BKSyfQ=wT%f$-4f=;K$nMsU^({zcUbnU@pRi(M%u+nuQ9P$nJkn7-i%~p8Q9L72JOWWX;ZQulP&}VdJbq9-T~ItcP&^w@-1;f* z>=ZX{iu*FfZI|M%N^#SqxF=HF@+j_T6gMo2`w_+MhW6kMHw%h;0mY@B;z~|&p{BSF zQ(R{0Al`=XHjKB+a4SZjA+W0EEP9yzL;Cp-||Z)?uIjz0>OL^FNnN z#7f?g%2&&;FT8&IQ{rUSCb~m(Ms&9nB)3v43%^>n!)?batYgxi{oE!S4%uL2!wsWe zCG_^=$E@?3J@?QiTMpS`WXp}9UgPPnTaKn!egEl*O{N_(&B(MHL$y|T_nu6#{;9X? zHd%GZDkH0I9m)+V=SEbUl+Mj4w+Pv*`4xNmu1)Saa@$eu;%e*B3Tud;KCsD*LuMG6al29N5wZJDR@lJw+q6yA9kR~Iy4#C#pOC#y znXqr%|J1I2)CYv#dOXG2$IcQqS#ih;BP(tknafggEGVacyI4?qXpER=G@we5VI&A^v z#gHoWtq|(VLJziU#nbO_-3|v^cGm;$ivZ0>@i+Qu#3qvtnPg}!i(QLZ1K!+*>Gz-Mxr@ZB2>wwWma2aNBBw*m delta 253 zcmZorxXq-ooR^o20SKzTBk@r46s5OpKTZ>YRF_WF{Co4u%t4lu&!ZS#>~L5 z8i*kvoiU0fg*})-lj9{wK$GznOF?2u#!E&hCnG;M^%g@B$UKl-USe*lpC;=q?v$*= zy!3p%(xRN=B9LLXSj#d~%ZrOxC;#ONh~Wc@fJ`X%0umpX85tRGFvwg$MGqLnFQB3i nYz(ZN4K5u*9U(K=F0ja7WRbtZBL9JziJz%~8w87ZfW`p;BD*`j diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2a36fd69370b38a98d8b01bf8eb9817c42f16ed6..5a8ad0fa900606f70fe29a5157433a49126bc8d6 100644 GIT binary patch literal 42340 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 literal 1364 zcmZ`(&1)M+6ra_{dS%J>I&SM!uH%hUh$T{~CE!96oTjQ1oHkCdQ$lozPf-Q6#oM~=1-7}AYwoe=&3h>36z}rW~~)fO5P4{-h1=r&HMPV zzfVm~BA|nRhVUX&zZ=1NEJDd#(kRMro?7~GlVKiARAFbK7y={g8`rq_)Wa;XDEltUWCmB zkq~sth&3ZeP{;A878u54V{Oaty2i>_vx<&kIwj51DaMXgGg(=)ND+pj!HI^Q9g`Br z#tzdA%!;PvWg3a1>()Z2BR!#4DWHiJ1dw>FOuS)~xge&2p-9tZ06jh%7)=`o)~k%rY>m)oo?Fy$ z*3WOp#5FJD)_FvD(?z&0py>SutcBjF^RHFyMAbU#a#vk`t*)G?D;+i6Rnx7Fo|by~ z_(Z$b)~@ZR_tUQyT0itB&pp5LYvy^Tl^e+D)6aYJ%l+g^CzEdC%omzVp>MZ5DOS%5y(&3}_iV*D$lT6G)7rh*{6u_h#PsdvEu>eN!ly zK#c!8&*`2Hz(;8eMy9Xq-w?`Q00RtN;6WRDimgDPfI9#y&j3~lTvZ2$wuScY(6Uvm zErMERS8mr7ZhExIP@4vR$aOEk#0?vRlk#5Z8~Ya&i6p>=7}yGiw%P=?rT~&z6=C)U zRWusRw(QgzfSd7Y@=t0?Km|zgsKSY=&97wcTk7rDyijDsbk+k{Z_8$PEwniZQ_b3 zr*UrF8`RMf&)g0|;&X-K1+M&dyQc9xirRtGU<9=Z<|8deJ;XMgHL}5tkT8l|x=DC` zov>wb)bWu=xaL#r?^eWHPRkTMbzI^%+bdj;eEK+&4#-2hSUNyf5;uPC%Rg{hZp4sV zQ&UI6^+%MrION%&)hl=HSHvjK|G@&$0p4Oe!gRCHwbXhaYX+_E)ja(aUMs=m3as&OMTiFIr2OvE#hP1Hz*02>`6XyD}5;U zWaly29z6dGZO)0uwIIXg#;p~^da4<;NWWFzf_>o%KHaRZ1RkkJ%aPxX>f|YDh=Z*D zBw%Y{3pGf+5in9eYVrE?^z?P7AI`Pb29vFu3aNx#bk)}3^`SVnbtlnaMc=xcub(8UGG0tZO~c#+LL5kuoIi@7 zdbS8+Yi7rq=~^>!X*S8I0%JDU!+&=Hi(!UuScK`SUc)#F;P;hG!FB z`Ee*`4o;2fTffVTT^hXX`=TWcyo&s0a1Ae~g_zb}uN)?&Tk3mRXrFR7dS(4HyDCii o-3=cKk*E;DLPXvYT>01EXQhf&c&j literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0002_jam_kelas_ruangan_tahunakademik_is_active_matakuliah_and_more.cpython-311.pyc b/core/migrations/__pycache__/0002_jam_kelas_ruangan_tahunakademik_is_active_matakuliah_and_more.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10a785b4e94246e01c9d6e2684435095becadca2 GIT binary patch literal 4002 zcmbtXOK=m}6>YVo*2j`*34_(L>=7||kc~eWVN5C&2e3T>V&+3aW|U8-@q3orlDb8I z3|1-?7O7O0?BXRWC#i8&ykNm%7FngTe5!g2)mu%~tk`J^uVyv(wImz-8L-k)U)|g9 z^||++`+oXIJRTL`y7ceA>i_K(gm1Xfxq@xw^*ktl7m$E_Wuf4MU$HD!`~`m{Pzd-0 zQTRwe{=Wz)fcna41@4k==zT)P+oXDaTy=ZBUZW=a|Dkj2Zd%4}$ z`#Iek+x4gcn()ll>Mb zUv>;(MJ)P(3;CF+yph8@qa-@*;q-J~MZxX9w|U(@^Mh=jJwaam2iZh{?bMbCk;C1( zpwb69R}OQI(m$L%8hA&0#%qstyyEy8%%Rq`2IID^o!Zmnj)n%ehYm(ulz{6Gj^TQ5 zUZLy>m^#;Wd_OuI9~$l&?femF7rI6pIgC~e0Cyk0le>#2;4as7e4~fsLt|Z|T{;47 zylb?HBhV(hMw>bU?Q+*>(}&T#IdSoDU5lYY0$qU}CC?&Dot*@>mB6xTVHTU3n!2p& zhVJNEnfdQ%6(;KFmy1liJ)aA(nC3dBx@=+1!HD%#Exn>y8)^w}u!xN^PC zn}y73YsU-T#bwR*TD4bwfW|_fx!^!ESfr#?HCuO}IqqnyuA!AQge!W9MRZ%$mK}W^ zgYOW+E1Fw&z(>^EKD{t44=d=oW6>qe(eAosU0Y>=5^S+K(Mob(l56OXT>J`g!*7>t z7PfH(+YYu^uxgn|uO&E-6QIK;U9lY(=|FD8G*sBAv4ffDn6(~`pKUvc&jVG)CqRZ) zsk$tPxLJ#OFrLnOy=>G@I<=(N_I8R=MRQ7SxpvBH149D~;;;33@Yy|l>5hgTLv4FK zY_;0<+XnR>cAl}os%Gi6hzH44=3mn)wXkP{#)2hYjCQP^&O%nJ8nu|$t8LxOC0SVY zh*nh=RaGF~Eo0DQs`|*)%DmXzLKvJQY%J^8w)h!{_1yAC+Y&$MSo}P~VmI~T4H$=u zd}y%9Er_h8#%>!9F5fZQN_baDr1_GY=VA`}BoAF2_`mF$Bmy2fKzMIs0xalWK zZu2Zvayy?9Wwx%&Qe~FN*WYmS_+4(&-MeWdGGyrT<_aCUTpzkZhprGg-wXvH!x+zS zqviTklxGNHmy70EA(G)l4mA2l0HBe)@N9%8FZ>geWWJuv(l*Qs)y$UlaXlGNCyL{nq+)HqFz6M3SM7$AdFn-A#V zRDE!o4o(yKN+XdX>9NfbnjYI(d7;ypMLP2_o%uEStx3|=db&!}RU$uX#zR0sl2h=f z76oVGJRuOfTr@NLMb5rQLdQ8>C275$)@fQN@>(Ntjtu9Y&(Pug%k{4|=-kJ2?sGc# z2ZA@qaIHREqr){K|FMx!$iSt|89H!jXZ^(nz4j5k_6fcAIr*JI2F&_^Ne4_KSDTR_ zq|gf~kPol5a!B;>96;=H(M;{I3qd6`~aD zN|7o>BCmSo%JFjLb{1dUruliAU!wV6kpdzqTu)(|!bDy07v%S4lBv`)6`H9K*?6O9#~t~& literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0003_kelas_prodi_ruangan_prodi.cpython-311.pyc b/core/migrations/__pycache__/0003_kelas_prodi_ruangan_prodi.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..965c5fc2e4c0090b8134f362fd5dfae32b6b8370 GIT binary patch literal 1222 zcmcIjJ8u&~5Z-&*cZnrpAwd)rf)XLz_#$W#Qb-&m9+6N22p6o@-i>|ddzgCwp&%&` zqD+^65MC(|zkq@tU?dmTwp4VEk^-e-_Uvjo%asCW0~b>6LvX zBlJyxY-mI0UyRxTL6;G|I5|YskM3t9_s!VaRGiX;q=mX5bsA^PN zMJ4UKfGZ`Lo1WbWNo)sx#C0#A%#G^9z3FFc2o8RLvx8Wgw<=SqAypL_G4(hTm8tke zcXC+(szNP!$H=gwkG%A@0y$X*M72GASA}X}4#r;Y_v-)1m;c}S8gg}<8o-yyjXN1n zK$=<)LdLJp&(B}R4)Jh{xg^42Li`5taZH+tPg;aB&u(Em!lWMCPZ;({Oj?O+lO`rU z#h!?vrAN8e4g+ehJ)aPP%K$(VFNzatmsGCRT;jL5>L;$tjljns5{ns^9v z6OX|bVYPLexs>OI^{kW%Jde{27;_UtVw$)N{BevQC&U$p1fs0Xe9HW~&7x3r49_o6 zx-5c)LK1E+Lv?n;zr{AVP93;yz-3zF`LrNtA)Z@YSY2FLs&F%Cvte7kLkB|5qq|l! z@R*g<5Lu3PN@+c7rh6w zHG29wG9E~MMLw->-R_&nIQQD@=BIjRrh5}-d&Sx9i{0Yv+qsWxpYMNp*sa{_RvvXL z>z&q%PI2>VakE?8?B`U&+(%#zh50qc%)iDcj1~2*o8k%2XT_IvJ~?2sOl#%Af3TcU z8cxDCZj9DbVNy8;x1SbK(oi$t9p$eM)V&tY0+e7zH86cek|bb6y3j#0gSUq+bxwNw Y`h=wS(cfmm1ftkLz5oCK literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0004_daftarhadir.cpython-311.pyc b/core/migrations/__pycache__/0004_daftarhadir.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..64f22425d40da56dbe1ebe71a1e4edf76686f2b7 GIT binary patch literal 1460 zcmZuxJ#5=X6h4v?B~i2@#d3wzt`!wQ5e2SjG)BD?bzG}*(>6hiWGDm_1f@GGlqga? zl1`MN@K6*Tv(;lJ6@@iq=#Zg9$Bq=hLAaqP&?%b&XUf!fr0Mi0kG#kC?!9~O-FNT! z)6$ZHfNuQyy*ZW;`img~G7l%d(9hG#mq%jBVhiRE_Be4~$grkuVl@Z2QI+ZM6V z6WWr9Na|H4EMh(*_cKWVoPsBfN)QEjQ(cD=w!XQ!d0ih6%W(A(b+D;Z->`dz9cwJ#G!8t2 zeqvygGSS3$TTHy!tf!c2_@1M8DKR{PS$;&#p+UzGY0Ppip{8M(4+&Eb3GFy8(QRW$ zSjL0UJ;P$^h)|CVArmZk#(}E~EQ85`;aeV42E-#YssidvcPzsmu$1jv7L)r1zHe9$ z^St;*tbk9>v32MVSjI%pVaxx_s*fz6!V)#!$>nG(bqNCUlH>!ESlDG5XB2hX?Rtr2 z^{8B3XNs;vIRI#Yuv^~-z^vrOVJn&*sX~DkCp8XlXDC0h0;4l$)ST@=nTm_cd+r6{5+x(a* z4uBY6kDt*g?~Ipw+C67TG{589p0AMyqzlQZcHf}`cVu*l#!=H=T`LU^e@n;F3E$%< z+!&21Z@h@{yW;)&L3yUHuz4Q26sCT8XSXOh&a|G-KXA7xtQ9k;VKeUl-xGu9x zG3E{2X~ch=Mgly&3T`ZSZja79I_4O$cX^a3lm^nv><4k!qm jT7WLc?;KqXUiU8KvLG+e|JfrYEM9re4KH3}jcWKC@)MO; literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0005_dosenpengampu_kelas_dosenpengampu_prodi_and_more.cpython-311.pyc b/core/migrations/__pycache__/0005_dosenpengampu_kelas_dosenpengampu_prodi_and_more.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6bbc985930832718301b04a2c3c79a433057c8b1 GIT binary patch literal 1533 zcmbtUOK;mo5MDklGFB|J2AsBW+r()S0)=D)&8;Y)I!=K;a)}e5I+R&y*AgX)RF)*7 zZwPy0~NGj7Ne`TmdG;H#o^ZXWj&xc zdM9F(*ETjbKKBUTO)%?W;xk?)QB1=T4ZCjO}KwgT{7MQn_{0MH~oS7l<6IbsI_0LyqLuo|rKr8M*(q|{5IE=_ur-F9od;z=By z$8FC8TxmdoU-Y~ODGmfSArT7k5*l`V8Z*()ywW7(mcYzJ!*Kf+MDKUQZ|Ng$l0FW* z5m!l@S7hAK;Cx}Ld1tG6bDP`EAfZg^a%-!HXY6}&`K|=GGsDO2Xhdfh}Ag092*}A9Bb3n z_385J)VVBhTz=Y~IGe}LX69_p(YPiw0QTB+tv=akPA-0x8=8OyvSVbQ6gj;ntVmNXa4#xf6m9n<`wT>Jo8vZ+ pTvHSU@~m8%pihc-imp%Qy}9{;V&>>|^2E++SDuTF(>c2m!+)CBj9LHy literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0006_jadwal_is_manual.cpython-311.pyc b/core/migrations/__pycache__/0006_jadwal_is_manual.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53a376907979564cf37e57749d81c79401134893 GIT binary patch literal 833 zcmZuvF>ll`6tMlXI6{B`$0)%8PP9P7NJCE%Xzvl|3`csCbS$ltg%{{xyhL8>ms<8T;UwW8r^Aq^ zg)kEiEH3&@ruw$$&NPt55n?F7fC3Hh00+8;m=QIk$Fiu;?QpAaTXXDiT zn~tnvL0b~Cg#DsnB54_Yo-?uD?RM7*@PefoO9wQ`O0vgdTKp5satR;qZ*joBS0`RVOZ=`EPi_{BoyE zho#TnvVJ+_xqra(y&|K1=Bozye=f(Dt$0Xg!pK6`TJ|nCWw{!ZQM{5cTFlBs_7<*m z?S?seUOC8K`RI(TJCo(Rlj|!JclGf8*j@d?&fMO2w>NfsRok$gb0pzHL8Y6nIoeV) zUv7@s_6%V|MAHFZgPmG^t$9Y|rrK8Ty(O(nYh%{JNBM2(s^6e^Euqpe#*!TN&d`Ia ZPi1znS)r?N=!~!3J68)A|5>fi{S7 - - - {% block title %}Knowledge Base{% endblock %} - {% if project_description %} - - - - {% endif %} - {% if project_image_url %} - - - {% endif %} - {% load static %} - - {% block head %}{% endblock %} + + + {% block title %}Sistem Jadwal Perkuliahan{% endblock %} + + + + + + + + + + {% load static %} + + + + {% block head %}{% endblock %} - - {% block content %}{% endblock %} - - + + + + +
+
+ +
+
+

Administrator

+ Super Admin +
+
+ +
+
+
+ +
+ {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + + {% block content %}{% endblock %} +
+
+ + + + + + + + + {% block scripts %}{% endblock %} + + \ No newline at end of file diff --git a/core/templates/core/buat_jadwal.html b/core/templates/core/buat_jadwal.html new file mode 100644 index 0000000..247bb0b --- /dev/null +++ b/core/templates/core/buat_jadwal.html @@ -0,0 +1,31 @@ +{% extends 'base.html' %} + +{% block title %}Buat Jadwal - SJP Admin{% endblock %} +{% block page_title %}Generate Jadwal Otomatis{% endblock %} + +{% block content %} +
+
+
+
+
+ +
+
+

Auto-Generate Jadwal

+

Sistem akan menyusun jadwal perkuliahan secara otomatis berdasarkan data master (Dosen Pengampu, Ruangan, Hari, Jam, dan Kelas) yang telah diinputkan. Sistem akan meminimalkan bentrok jadwal.

+ +
+ Pastikan semua data master telah terisi lengkap sebelum memulai proses generate. +
+ +
+ {% csrf_token %} + +
+
+
+
+{% endblock %} diff --git a/core/templates/core/daftar_hadir_list.html b/core/templates/core/daftar_hadir_list.html new file mode 100644 index 0000000..7db4305 --- /dev/null +++ b/core/templates/core/daftar_hadir_list.html @@ -0,0 +1,175 @@ +{% extends 'base.html' %} + +{% block title %}Daftar Hadir Perkuliahan{% endblock %} +{% block page_title %}Daftar Hadir Perkuliahan{% endblock %} + +{% block content %} +
+
+ +
+
+ {% csrf_token %} + +
+
+ +
+
+
+ + + + + + + + + + + + + + {% for hadir in hadir_all %} + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
TanggalPertemuanJadwal / MatkulDosenKeteranganAksi
+ + {{ hadir.tanggal|date:"d/m/Y" }}Ke-{{ hadir.pertemuan_ke }} +
{{ hadir.jadwal.dosen_pengampu.matkul.nama }}
+ {{ hadir.jadwal.hari.nama }}, {{ hadir.jadwal.jam.range_waktu }} +
{{ hadir.jadwal.dosen_pengampu.dosen.nama }}{{ hadir.keterangan|default:"-" }} + + + + +
Belum ada data kehadiran.
+
+
+
+ + + + + + +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/core/templates/core/dashboard.html b/core/templates/core/dashboard.html new file mode 100644 index 0000000..aba2818 --- /dev/null +++ b/core/templates/core/dashboard.html @@ -0,0 +1,145 @@ +{% extends 'base.html' %} + +{% block title %}Dashboard - SJP Admin{% endblock %} +{% block page_title %}Dashboard Overview{% endblock %} + +{% block content %} +
+ +
+
+
+
+
+ +
+
+

{{ count_prodi }}

+

Program Studi

+
+
+
+
+
+
+
+
+ +
+
+

{{ count_dosen }}

+

Total Dosen

+
+
+
+
+
+
+
+
+ +
+
+

{{ count_matkul }}

+

Mata Kuliah

+
+
+
+
+
+
+
+
+ +
+
+

{{ count_daftar_hadir }}

+

Log Kehadiran

+
+
+
+
+ +
+ +
+
+
+
Status Sistem
+
+
+
+
+
+
Jadwal Terdaftar
+

{{ count_jadwal }} Entry

+
+
+
+
+
+
Kelas Aktif
+

{{ count_kelas }} Kelas

+
+
+
+
+
+
Ruangan Terdaftar
+

{{ count_ruangan }} Ruangan

+
+
+
+
+

Versi Aplikasi 1.1.0

+
+
+
+
+
+{% endblock %} diff --git a/core/templates/core/dosen_list.html b/core/templates/core/dosen_list.html new file mode 100644 index 0000000..9a09628 --- /dev/null +++ b/core/templates/core/dosen_list.html @@ -0,0 +1,162 @@ +{% extends 'base.html' %} + +{% block title %}Daftar Dosen{% endblock %} +{% block page_title %}Daftar Dosen{% endblock %} + +{% block content %} +
+
+ + + + Template + +
+
+ {% csrf_token %} + +
+
+ +
+
+
+ + + + + + + + + + + {% for dosen in dosen_all %} + + + + + + + {% empty %} + + + + {% endfor %} + +
NIDNNama DosenAksi
+ + {{ dosen.nidn }}{{ dosen.nama }} + + + + +
Belum ada data dosen.
+
+
+
+ + + + + + + + + + +{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/hari_list.html b/core/templates/core/hari_list.html new file mode 100644 index 0000000..484fcdd --- /dev/null +++ b/core/templates/core/hari_list.html @@ -0,0 +1,26 @@ +{% extends 'base.html' %} +{% block title %}Daftar Hari{% endblock %} +{% block page_title %}Daftar Hari{% endblock %} +{% block content %} +
+ +
{% csrf_token %}
+
+
+{% for h in hari_all %} + + + + + +{% endfor %} +
Nama HariAksi
{{ h.nama }} + + +
+ + +{% endblock %} +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/jadwal_list.html b/core/templates/core/jadwal_list.html new file mode 100644 index 0000000..f0afe3b --- /dev/null +++ b/core/templates/core/jadwal_list.html @@ -0,0 +1,257 @@ +{% extends 'base.html' %} + +{% block title %}Hasil Jadwal Perkuliahan{% endblock %} +{% block page_title %}Hasil Jadwal Perkuliahan{% endblock %} + +{% block content %} + + +
+
+
+ {% csrf_token %} + +
+ + + + + + + + + + + + + + + + + {% for jadwal in jadwal_all %} + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
Hari & JamKode MKMata KuliahDosenProdiSem.KelasRuanganAksi
+
{{ jadwal.hari.nama }}
+ {{ jadwal.jam.range_waktu }} +
{{ jadwal.dosen_pengampu.matkul.kode }} +
{{ jadwal.dosen_pengampu.matkul.nama }}
+ {% if jadwal.is_manual %} + Manual + {% endif %} +
{{ jadwal.dosen_pengampu.dosen.nama }}{{ jadwal.dosen_pengampu.matkul.prodi.nama }}{{ jadwal.dosen_pengampu.matkul.semester }}{{ jadwal.kelas.nama }}{{ jadwal.ruangan.nama }} +
+ + + + +
+
Belum ada jadwal yang digenerate.
+
+ {% if jadwal_all %} +
+ +
+ {% endif %} +
+
+
+ + + + + + + + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/jam_list.html b/core/templates/core/jam_list.html new file mode 100644 index 0000000..c7e07bd --- /dev/null +++ b/core/templates/core/jam_list.html @@ -0,0 +1,168 @@ +{% extends 'base.html' %} + +{% block title %}Daftar Waktu / Jam{% endblock %} +{% block page_title %}Daftar Waktu / Jam{% endblock %} + +{% block content %} +
+
+ + +
+
+ {% csrf_token %} + +
+
+ +
+
+
+ + + + + + + + + + {% for jam in jam_all %} + + + + + + {% empty %} + + + + {% endfor %} + +
Range WaktuAksi
+ + {{ jam.range_waktu }} + + + + +
Belum ada data jam.
+
+
+
+ + + + + + + + + +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/core/templates/core/kelas_list.html b/core/templates/core/kelas_list.html new file mode 100644 index 0000000..ed273f3 --- /dev/null +++ b/core/templates/core/kelas_list.html @@ -0,0 +1,170 @@ +{% extends 'base.html' %} + +{% block title %}Daftar Kelas{% endblock %} +{% block page_title %}Daftar Kelas{% endblock %} + +{% block content %} +
+
+ + + + Template + +
+
+ {% csrf_token %} + +
+
+ +
+
+
+ + + + + + + + + + + {% for kelas in kelas_all %} + + + + + + + {% empty %} + + + + {% endfor %} + +
Nama KelasProgram StudiAksi
+ + {{ kelas.nama }}{{ kelas.prodi.nama|default:"-" }} + + + + +
Belum ada data kelas.
+
+
+
+ + + + + + + + + +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/core/templates/core/matkul_list.html b/core/templates/core/matkul_list.html new file mode 100644 index 0000000..02e8260 --- /dev/null +++ b/core/templates/core/matkul_list.html @@ -0,0 +1,211 @@ +{% extends 'base.html' %} + +{% block title %}Daftar Mata Kuliah{% endblock %} +{% block page_title %}Daftar Mata Kuliah{% endblock %} + +{% block content %} +
+
+ + + + Template + +
+
+ {% csrf_token %} + +
+
+ +
+
+
+ + + + + + + + + + + + + + {% for matkul in matkul_all %} + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
KodeNama MatkulSKSSemesterProdiAksi
+ + {{ matkul.kode }}{{ matkul.nama }}{{ matkul.sks }}{{ matkul.semester }}{{ matkul.prodi.nama }} + + + + +
Belum ada data mata kuliah.
+
+
+
+ + + + + + + + + + +{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/pengampu_list.html b/core/templates/core/pengampu_list.html new file mode 100644 index 0000000..bd55388 --- /dev/null +++ b/core/templates/core/pengampu_list.html @@ -0,0 +1,267 @@ +{% extends 'base.html' %} + +{% block title %}Daftar Dosen Pengampu{% endblock %} +{% block page_title %}Daftar Dosen Pengampu{% endblock %} + +{% block content %} +
+
+ +
+
+ {% csrf_token %} + +
+
+ +
+
+
+ + + + + + + + + + + + + + {% for pengampu in pengampu_all %} + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
DosenProgram StudiMata KuliahKelasTahun AkademikAksi
+ + +
{{ pengampu.dosen.nama }}
+ NIDN: {{ pengampu.dosen.nidn }} +
{{ pengampu.prodi.nama|default:"-" }} +
{{ pengampu.matkul.nama }}
+ {{ pengampu.matkul.kode }} ({{ pengampu.matkul.sks }} SKS) +
{{ pengampu.kelas.nama|default:"-" }}{{ pengampu.tahun_akademik.nama }} + + + + +
Belum ada data dosen pengampu.
+
+
+
+ + + + + + +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/core/templates/core/prodi_list.html b/core/templates/core/prodi_list.html new file mode 100644 index 0000000..58041c5 --- /dev/null +++ b/core/templates/core/prodi_list.html @@ -0,0 +1,26 @@ +{% extends 'base.html' %} +{% block title %}Program Studi{% endblock %} +{% block page_title %}Program Studi{% endblock %} +{% block content %} +
+ +
{% csrf_token %}
+
+
+{% for p in prodi_all %} + + + + + +{% endfor %} +
Nama Program StudiAksi
{{ p.nama }} + + +
+ + +{% endblock %} +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/ruangan_list.html b/core/templates/core/ruangan_list.html new file mode 100644 index 0000000..1c4c27a --- /dev/null +++ b/core/templates/core/ruangan_list.html @@ -0,0 +1,182 @@ +{% extends 'base.html' %} + +{% block title %}Daftar Ruangan{% endblock %} +{% block page_title %}Daftar Ruangan{% endblock %} + +{% block content %} +
+
+ + + + Template + +
+
+ {% csrf_token %} + +
+
+ +
+
+
+ + + + + + + + + + + + {% for ruangan in ruangan_all %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
Nama RuanganKapasitasProgram StudiAksi
+ + {{ ruangan.nama }}{{ ruangan.kapasitas }} Kursi{{ ruangan.prodi.nama|default:"-" }} + + + + +
Belum ada data ruangan.
+
+
+
+ + + + + + + + + +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/core/templates/core/tahun_list.html b/core/templates/core/tahun_list.html new file mode 100644 index 0000000..7cab57f --- /dev/null +++ b/core/templates/core/tahun_list.html @@ -0,0 +1,27 @@ +{% extends 'base.html' %} +{% block title %}Tahun Akademik{% endblock %} +{% block page_title %}Tahun Akademik{% endblock %} +{% block content %} +
+ +
{% csrf_token %}
+
+
+{% for t in tahun_all %} + + + + + + +{% endfor %} +
Tahun AkademikStatusAksi
{{ t.nama }}{% if t.is_active %}Aktif{% else %}Non-Aktif{% endif %} + + +
+ + +{% endblock %} +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/core/urls.py b/core/urls.py index 6299e3d..9aec7cc 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,54 @@ from django.urls import path - -from .views import home +from . import views urlpatterns = [ - path("", home, name="home"), -] + path('', views.dashboard, name='dashboard'), + + # AJAX Helpers + path('ajax/get-kelas/', views.get_kelas_by_prodi, name='get_kelas_by_prodi'), + path('ajax/get-matkul/', views.get_matkul_by_prodi, name='get_matkul_by_prodi'), + + path('prodi/', views.prodi_list, name='prodi_list'), + path('prodi/delete//', views.prodi_delete, name='prodi_delete'), + + path('dosen/', views.dosen_list, name='dosen_list'), + path('dosen/delete//', views.dosen_delete, name='dosen_delete'), + path('dosen/template/', views.dosen_template, name='dosen_template'), + path('dosen/import/', views.dosen_import, name='dosen_import'), + + path('matkul/', views.matkul_list, name='matkul_list'), + path('matkul/delete//', views.matkul_delete, name='matkul_delete'), + path('matkul/template/', views.matkul_template, name='matkul_template'), + path('matkul/import/', views.matkul_import, name='matkul_import'), + + path('ruangan/', views.ruangan_list, name='ruangan_list'), + path('ruangan/delete//', views.ruangan_delete, name='ruangan_delete'), + path('ruangan/template/', views.ruangan_template, name='ruangan_template'), + path('ruangan/import/', views.ruangan_import, name='ruangan_import'), + + path('kelas/', views.kelas_list, name='kelas_list'), + path('kelas/delete//', views.kelas_delete, name='kelas_delete'), + path('kelas/template/', views.kelas_template, name='kelas_template'), + path('kelas/import/', views.kelas_import, name='kelas_import'), + + path('jam/', views.jam_list, name='jam_list'), + path('jam/delete//', views.jam_delete, name='jam_delete'), + path('jam/generate/', views.jam_generate, name='jam_generate'), + + path('tahun/', views.tahun_list, name='tahun_list'), + path('tahun/delete//', views.tahun_delete, name='tahun_delete'), + + path('hari/', views.hari_list, name='hari_list'), + path('hari/delete//', views.hari_delete, name='hari_delete'), + + path('pengampu/', views.pengampu_list, name='pengampu_list'), + path('pengampu/delete//', views.pengampu_delete, name='pengampu_delete'), + + path('daftar-hadir/', views.daftar_hadir_list, name='daftar_hadir_list'), + path('daftar-hadir/delete//', views.daftar_hadir_delete, name='daftar_hadir_delete'), + + path('jadwal/', views.jadwal_list, name='jadwal_list'), + path('jadwal/buat/', views.buat_jadwal, name='buat_jadwal'), + path('jadwal/delete//', views.jadwal_delete, name='jadwal_delete'), + path('jadwal/export/', views.jadwal_export_excel, name='jadwal_export_excel'), +] \ No newline at end of file diff --git a/core/views.py b/core/views.py index c9aed12..9a3c43f 100644 --- a/core/views.py +++ b/core/views.py @@ -1,25 +1,651 @@ -import os -import platform - -from django import get_version as django_version -from django.shortcuts import render -from django.utils import timezone - - -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() +import csv +import datetime +import random +from django.shortcuts import render, redirect, get_object_or_404 +from django.http import HttpResponse, JsonResponse +from django.contrib import messages +from django.db import IntegrityError +from openpyxl import Workbook +from .models import ( + ProgramStudi, Dosen, TahunAkademik, Hari, + MataKuliah, Ruangan, Kelas, Jam, DosenPengampu, Jadwal, DaftarHadir +) +def dashboard(request): 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", ""), + "count_prodi": ProgramStudi.objects.count(), + "count_dosen": Dosen.objects.count(), + "count_matkul": MataKuliah.objects.count(), + "count_ruangan": Ruangan.objects.count(), + "count_kelas": Kelas.objects.count(), + "count_jadwal": Jadwal.objects.count(), + "count_daftar_hadir": DaftarHadir.objects.count(), } - return render(request, "core/index.html", context) + return render(request, "core/dashboard.html", context) + +# --- AJAX HELPERS --- +def get_kelas_by_prodi(request): + prodi_id = request.GET.get('prodi_id') + kelas = Kelas.objects.filter(prodi_id=prodi_id).values('id', 'nama') + return JsonResponse(list(kelas), safe=False) + +def get_matkul_by_prodi(request): + prodi_id = request.GET.get('prodi_id') + matkul = MataKuliah.objects.filter(prodi_id=prodi_id).values('id', 'kode', 'nama') + return JsonResponse(list(matkul), safe=False) + +# --- PRODI --- +def prodi_list(request): + prodi_all = ProgramStudi.objects.all() + if request.method == "POST": + if "delete_massal" in request.POST: + ids = request.POST.getlist("selected_ids") + ProgramStudi.objects.filter(id__in=ids).delete() + messages.success(request, f"Berhasil menghapus {len(ids)} Program Studi.") + else: + pk = request.POST.get("pk") + nama = request.POST.get("nama") + if pk: + obj = get_object_or_404(ProgramStudi, pk=pk) + obj.nama = nama + obj.save() + messages.success(request, "Program Studi berhasil diupdate.") + else: + ProgramStudi.objects.create(nama=nama) + messages.success(request, "Program Studi berhasil ditambahkan.") + return redirect("prodi_list") + return render(request, "core/prodi_list.html", {"prodi_all": prodi_all}) + +def prodi_delete(request, pk): + get_object_or_404(ProgramStudi, pk=pk).delete() + messages.success(request, "Program Studi berhasil dihapus.") + return redirect("prodi_list") + +# --- DOSEN --- +def dosen_list(request): + dosen_all = Dosen.objects.all() + if request.method == "POST": + if "delete_massal" in request.POST: + ids = request.POST.getlist("selected_ids") + Dosen.objects.filter(id__in=ids).delete() + messages.success(request, f"Berhasil menghapus {len(ids)} Dosen.") + else: + pk = request.POST.get("pk") + nidn = request.POST.get("nidn") + nama = request.POST.get("nama") + if pk: + obj = get_object_or_404(Dosen, pk=pk) + obj.nidn = nidn + obj.nama = nama + obj.save() + messages.success(request, "Dosen berhasil diupdate.") + else: + Dosen.objects.create(nidn=nidn, nama=nama) + messages.success(request, "Dosen berhasil ditambahkan.") + return redirect("dosen_list") + return render(request, "core/dosen_list.html", {"dosen_all": dosen_all}) + +def dosen_delete(request, pk): + get_object_or_404(Dosen, pk=pk).delete() + messages.success(request, "Dosen berhasil dihapus.") + return redirect("dosen_list") + +def dosen_template(request): + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="template_dosen.csv"' + writer = csv.writer(response) + writer.writerow(['nidn', 'nama']) + writer.writerow(['12345678', 'Dr. Nama Dosen, M.Kom']) + return response + +def dosen_import(request): + if request.method == 'POST' and request.FILES.get('csv_file'): + csv_file = request.FILES['csv_file'] + decoded_file = csv_file.read().decode('utf-8').splitlines() + reader = csv.DictReader(decoded_file) + count = 0 + for row in reader: + Dosen.objects.update_or_create( + nidn=row['nidn'], + defaults={'nama': row['nama']} + ) + count += 1 + messages.success(request, f"Berhasil mengimport {count} data Dosen.") + return redirect('dosen_list') + +# --- MATA KULIAH --- +def matkul_list(request): + matkul_all = MataKuliah.objects.all() + prodi_all = ProgramStudi.objects.all() + if request.method == "POST": + if "delete_massal" in request.POST: + ids = request.POST.getlist("selected_ids") + MataKuliah.objects.filter(id__in=ids).delete() + messages.success(request, f"Berhasil menghapus {len(ids)} Mata Kuliah.") + else: + pk = request.POST.get("pk") + kode = request.POST.get("kode") + nama = request.POST.get("nama") + sks = request.POST.get("sks") + semester = request.POST.get("semester") + prodi_id = request.POST.get("prodi") + prodi = get_object_or_404(ProgramStudi, id=prodi_id) + try: + if pk: + obj = get_object_or_404(MataKuliah, pk=pk) + obj.kode = kode + obj.nama = nama + obj.sks = sks + obj.semester = semester + obj.prodi = prodi + obj.save() + messages.success(request, "Mata Kuliah berhasil diupdate.") + else: + MataKuliah.objects.create(kode=kode, nama=nama, sks=sks, semester=semester, prodi=prodi) + messages.success(request, "Mata Kuliah berhasil ditambahkan.") + except IntegrityError: + messages.error(request, "Gagal menyimpan. Kode atau Nama Mata Kuliah sudah ada di Program Studi ini.") + return redirect("matkul_list") + return render(request, "core/matkul_list.html", {"matkul_all": matkul_all, "prodi_all": prodi_all}) + +def matkul_delete(request, pk): + get_object_or_404(MataKuliah, pk=pk).delete() + messages.success(request, "Mata Kuliah berhasil dihapus.") + return redirect("matkul_list") + +def matkul_template(request): + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="template_matkul.csv"' + writer = csv.writer(response) + writer.writerow(['kode', 'nama', 'sks', 'semester', 'program_studi']) + writer.writerow(['MK001', 'Algoritma Pemrograman', '3', '1', 'Teknik Informatika']) + return response + +def matkul_import(request): + if request.method == 'POST' and request.FILES.get('csv_file'): + csv_file = request.FILES['csv_file'] + decoded_file = csv_file.read().decode('utf-8').splitlines() + reader = csv.DictReader(decoded_file) + count = 0 + for row in reader: + prodi_nama = row.get('program_studi') + prodi, _ = ProgramStudi.objects.get_or_create(nama=prodi_nama) + MataKuliah.objects.update_or_create( + kode=row['kode'], + prodi=prodi, + defaults={ + 'nama': row['nama'], + 'sks': row['sks'], + 'semester': row['semester'] + } + ) + count += 1 + messages.success(request, f"Berhasil mengimport {count} data Mata Kuliah.") + return redirect('matkul_list') + +# --- RUANGAN --- +def ruangan_list(request): + ruangan_all = Ruangan.objects.all() + prodi_all = ProgramStudi.objects.all() + if request.method == "POST": + if "delete_massal" in request.POST: + ids = request.POST.getlist("selected_ids") + Ruangan.objects.filter(id__in=ids).delete() + messages.success(request, f"Berhasil menghapus {len(ids)} Ruangan.") + else: + pk = request.POST.get("pk") + nama = request.POST.get("nama") + kapasitas = request.POST.get("kapasitas") + prodi_id = request.POST.get("prodi") + prodi = get_object_or_404(ProgramStudi, id=prodi_id) if prodi_id else None + if pk: + obj = get_object_or_404(Ruangan, pk=pk) + obj.nama = nama + obj.kapasitas = kapasitas + obj.prodi = prodi + obj.save() + messages.success(request, "Ruangan berhasil diupdate.") + else: + Ruangan.objects.create(nama=nama, kapasitas=kapasitas, prodi=prodi) + messages.success(request, "Ruangan berhasil ditambahkan.") + return redirect("ruangan_list") + return render(request, "core/ruangan_list.html", {"ruangan_all": ruangan_all, "prodi_all": prodi_all}) + +def ruangan_delete(request, pk): + get_object_or_404(Ruangan, pk=pk).delete() + messages.success(request, "Ruangan berhasil dihapus.") + return redirect("ruangan_list") + +def ruangan_template(request): + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="template_ruangan.csv"' + writer = csv.writer(response) + writer.writerow(['nama', 'kapasitas', 'program_studi']) + writer.writerow(['Ruang A1', '40', 'Teknik Informatika']) + return response + +def ruangan_import(request): + if request.method == 'POST' and request.FILES.get('csv_file'): + csv_file = request.FILES['csv_file'] + decoded_file = csv_file.read().decode('utf-8').splitlines() + reader = csv.DictReader(decoded_file) + count = 0 + for row in reader: + prodi_nama = row.get('program_studi') + prodi = None + if prodi_nama: + prodi, _ = ProgramStudi.objects.get_or_create(nama=prodi_nama) + Ruangan.objects.create(nama=row['nama'], kapasitas=row['kapasitas'], prodi=prodi) + count += 1 + messages.success(request, f"Berhasil mengimport {count} data Ruangan.") + return redirect('ruangan_list') + +# --- KELAS --- +def kelas_list(request): + kelas_all = Kelas.objects.all() + prodi_all = ProgramStudi.objects.all() + if request.method == "POST": + if "delete_massal" in request.POST: + ids = request.POST.getlist("selected_ids") + Kelas.objects.filter(id__in=ids).delete() + messages.success(request, f"Berhasil menghapus {len(ids)} Kelas.") + else: + pk = request.POST.get("pk") + nama = request.POST.get("nama") + prodi_id = request.POST.get("prodi") + prodi = get_object_or_404(ProgramStudi, id=prodi_id) if prodi_id else None + if pk: + obj = get_object_or_404(Kelas, pk=pk) + obj.nama = nama + obj.prodi = prodi + obj.save() + messages.success(request, "Kelas berhasil diupdate.") + else: + Kelas.objects.create(nama=nama, prodi=prodi) + messages.success(request, "Kelas berhasil ditambahkan.") + return redirect("kelas_list") + return render(request, "core/kelas_list.html", {"kelas_all": kelas_all, "prodi_all": prodi_all}) + +def kelas_delete(request, pk): + get_object_or_404(Kelas, pk=pk).delete() + messages.success(request, "Kelas berhasil dihapus.") + return redirect("kelas_list") + +def kelas_template(request): + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="template_kelas.csv"' + writer = csv.writer(response) + writer.writerow(['nama', 'program_studi']) + writer.writerow(['Kelas A', 'Teknik Informatika']) + return response + +def kelas_import(request): + if request.method == 'POST' and request.FILES.get('csv_file'): + csv_file = request.FILES['csv_file'] + decoded_file = csv_file.read().decode('utf-8').splitlines() + reader = csv.DictReader(decoded_file) + count = 0 + for row in reader: + prodi_nama = row.get('program_studi') + prodi = None + if prodi_nama: + prodi, _ = ProgramStudi.objects.get_or_create(nama=prodi_nama) + Kelas.objects.create(nama=row['nama'], prodi=prodi) + count += 1 + messages.success(request, f"Berhasil mengimport {count} data Kelas.") + return redirect('kelas_list') + +# --- JAM --- +def jam_list(request): + jam_all = Jam.objects.all() + if request.method == "POST": + if "delete_massal" in request.POST: + ids = request.POST.getlist("selected_ids") + Jam.objects.filter(id__in=ids).delete() + messages.success(request, f"Berhasil menghapus {len(ids)} Jam.") + else: + pk = request.POST.get("pk") + range_waktu = request.POST.get("range_waktu") + if pk: + obj = get_object_or_404(Jam, pk=pk) + obj.range_waktu = range_waktu + obj.save() + messages.success(request, "Jam berhasil diupdate.") + else: + Jam.objects.create(range_waktu=range_waktu) + messages.success(request, "Jam berhasil ditambahkan.") + return redirect("jam_list") + return render(request, "core/jam_list.html", {"jam_all": jam_all}) + +def jam_delete(request, pk): + get_object_or_404(Jam, pk=pk).delete() + messages.success(request, "Jam berhasil dihapus.") + return redirect("jam_list") + +def jam_generate(request): + if request.method == "POST": + start_str = request.POST.get("jam_mulai") # e.g. "07:00" + end_str = request.POST.get("jam_selesai") # e.g. "17:00" + mins_per_sks = int(request.POST.get("durasi_per_sks", 50)) + sks_per_slot = int(request.POST.get("jumlah_sks", 2)) + jeda_mins = int(request.POST.get("jeda", 0)) + + start_time = datetime.datetime.strptime(start_str, "%H:%M") + end_time = datetime.datetime.strptime(end_str, "%H:%M") + + slot_duration = mins_per_sks * sks_per_slot + current_time = start_time + + count = 0 + while current_time + datetime.timedelta(minutes=slot_duration) <= end_time: + slot_end = current_time + datetime.timedelta(minutes=slot_duration) + range_waktu = f"{current_time.strftime('%H:%M')} - {slot_end.strftime('%H:%M')}" + Jam.objects.get_or_create(range_waktu=range_waktu) + current_time = slot_end + datetime.timedelta(minutes=jeda_mins) + count += 1 + + messages.success(request, f"Berhasil menggenerate {count} slot waktu.") + return redirect("jam_list") + +# --- TAHUN AKADEMIK --- +def tahun_list(request): + tahun_all = TahunAkademik.objects.all() + if request.method == "POST": + if "delete_massal" in request.POST: + ids = request.POST.getlist("selected_ids") + TahunAkademik.objects.filter(id__in=ids).delete() + messages.success(request, f"Berhasil menghapus {len(ids)} Tahun Akademik.") + else: + pk = request.POST.get("pk") + nama = request.POST.get("nama") + is_active = request.POST.get("is_active") == "on" + if pk: + obj = get_object_or_404(TahunAkademik, pk=pk) + if is_active: + TahunAkademik.objects.update(is_active=False) + obj.nama = nama + obj.is_active = is_active + obj.save() + messages.success(request, "Tahun Akademik berhasil diupdate.") + else: + if is_active: + TahunAkademik.objects.update(is_active=False) + TahunAkademik.objects.create(nama=nama, is_active=is_active) + messages.success(request, "Tahun Akademik berhasil ditambahkan.") + return redirect("tahun_list") + return render(request, "core/tahun_list.html", {"tahun_all": tahun_all}) + +def tahun_delete(request, pk): + get_object_or_404(TahunAkademik, pk=pk).delete() + messages.success(request, "Tahun Akademik berhasil dihapus.") + return redirect("tahun_list") + +# --- HARI --- +def hari_list(request): + hari_all = Hari.objects.all() + if request.method == "POST": + if "delete_massal" in request.POST: + ids = request.POST.getlist("selected_ids") + Hari.objects.filter(id__in=ids).delete() + messages.success(request, f"Berhasil menghapus {len(ids)} Hari.") + else: + pk = request.POST.get("pk") + nama = request.POST.get("nama") + if pk: + obj = get_object_or_404(Hari, pk=pk) + obj.nama = nama + obj.save() + messages.success(request, "Hari berhasil diupdate.") + else: + Hari.objects.create(nama=nama) + messages.success(request, "Hari berhasil ditambahkan.") + return redirect("hari_list") + return render(request, "core/hari_list.html", {"hari_all": hari_all}) + +def hari_delete(request, pk): + get_object_or_404(Hari, pk=pk).delete() + messages.success(request, "Hari berhasil dihapus.") + return redirect("hari_list") + +# --- DOSEN PENGAMPU --- +def pengampu_list(request): + pengampu_all = DosenPengampu.objects.all() + dosen_all = Dosen.objects.all() + matkul_all = MataKuliah.objects.all() + tahun_all = TahunAkademik.objects.all() + prodi_all = ProgramStudi.objects.all() + kelas_all = Kelas.objects.all() + if request.method == "POST": + if "delete_massal" in request.POST: + ids = request.POST.getlist("selected_ids") + DosenPengampu.objects.filter(id__in=ids).delete() + messages.success(request, f"Berhasil menghapus {len(ids)} Dosen Pengampu.") + else: + pk = request.POST.get("pk") + dosen_id = request.POST.get("dosen") + matkul_id = request.POST.get("matkul") + tahun_id = request.POST.get("tahun") + prodi_id = request.POST.get("prodi") + kelas_id = request.POST.get("kelas") + + dosen = get_object_or_404(Dosen, id=dosen_id) + matkul = get_object_or_404(MataKuliah, id=matkul_id) + tahun = get_object_or_404(TahunAkademik, id=tahun_id) + prodi = get_object_or_404(ProgramStudi, id=prodi_id) if prodi_id else None + kelas = get_object_or_404(Kelas, id=kelas_id) if kelas_id else None + + if pk: + obj = get_object_or_404(DosenPengampu, pk=pk) + obj.dosen = dosen + obj.matkul = matkul + obj.tahun_akademik = tahun + obj.prodi = prodi + obj.kelas = kelas + obj.save() + messages.success(request, "Dosen Pengampu berhasil diupdate.") + else: + DosenPengampu.objects.create( + dosen=dosen, matkul=matkul, tahun_akademik=tahun, + prodi=prodi, kelas=kelas + ) + messages.success(request, "Dosen Pengampu berhasil ditambahkan.") + return redirect("pengampu_list") + return render(request, "core/pengampu_list.html", { + "pengampu_all": pengampu_all, + "dosen_all": dosen_all, + "matkul_all": matkul_all, + "tahun_all": tahun_all, + "prodi_all": prodi_all, + "kelas_all": kelas_all, + }) + +def pengampu_delete(request, pk): + get_object_or_404(DosenPengampu, pk=pk).delete() + messages.success(request, "Dosen Pengampu berhasil dihapus.") + return redirect("pengampu_list") + +# --- DAFTAR HADIR --- +def daftar_hadir_list(request): + hadir_all = DaftarHadir.objects.all() + jadwal_all = Jadwal.objects.all() + if request.method == "POST": + if "delete_massal" in request.POST: + ids = request.POST.getlist("selected_ids") + DaftarHadir.objects.filter(id__in=ids).delete() + messages.success(request, f"Berhasil menghapus {len(ids)} Daftar Hadir.") + else: + pk = request.POST.get("pk") + jadwal_id = request.POST.get("jadwal") + tanggal = request.POST.get("tanggal") + pertemuan = request.POST.get("pertemuan_ke") + ket = request.POST.get("keterangan") + jadwal = get_object_or_404(Jadwal, id=jadwal_id) + if pk: + obj = get_object_or_404(DaftarHadir, pk=pk) + obj.jadwal = jadwal + obj.tanggal = tanggal + obj.pertemuan_ke = pertemuan + obj.keterangan = ket + obj.save() + messages.success(request, "Daftar Hadir berhasil diupdate.") + else: + DaftarHadir.objects.create(jadwal=jadwal, tanggal=tanggal, pertemuan_ke=pertemuan, keterangan=ket) + messages.success(request, "Daftar Hadir berhasil ditambahkan.") + return redirect("daftar_hadir_list") + return render(request, "core/daftar_hadir_list.html", {"hadir_all": hadir_all, "jadwal_all": jadwal_all}) + +def daftar_hadir_delete(request, pk): + get_object_or_404(DaftarHadir, pk=pk).delete() + messages.success(request, "Daftar Hadir berhasil dihapus.") + return redirect("daftar_hadir_list") + +# --- JADWAL --- +def jadwal_list(request): + jadwal_all = Jadwal.objects.all().order_by('hari', 'jam') + pengampu_all = DosenPengampu.objects.all() + ruangan_all = Ruangan.objects.all() + hari_all = Hari.objects.all() + jam_all = Jam.objects.all() + kelas_all = Kelas.objects.all() + + if request.method == "POST": + if "delete_massal" in request.POST: + ids = request.POST.getlist("selected_ids") + Jadwal.objects.filter(id__in=ids).delete() + messages.success(request, f"Berhasil menghapus {len(ids)} Jadwal.") + else: + pk = request.POST.get("pk") + pengampu_id = request.POST.get("pengampu") + ruangan_id = request.POST.get("ruangan") + hari_id = request.POST.get("hari") + jam_id = request.POST.get("jam") + kelas_id = request.POST.get("kelas") + + pengampu = get_object_or_404(DosenPengampu, id=pengampu_id) + ruangan = get_object_or_404(Ruangan, id=ruangan_id) + hari = get_object_or_404(Hari, id=hari_id) + jam = get_object_or_404(Jam, id=jam_id) + kelas = get_object_or_404(Kelas, id=kelas_id) + + if pk: + obj = get_object_or_404(Jadwal, pk=pk) + obj.dosen_pengampu = pengampu + obj.ruangan = ruangan + obj.hari = hari + obj.jam = jam + obj.kelas = kelas + obj.save() + messages.success(request, "Jadwal berhasil diupdate.") + else: + Jadwal.objects.create( + dosen_pengampu=pengampu, ruangan=ruangan, + hari=hari, jam=jam, kelas=kelas, is_manual=True + ) + messages.success(request, "Jadwal berhasil ditambahkan secara manual.") + return redirect("jadwal_list") + + return render(request, "core/jadwal_list.html", { + "jadwal_all": jadwal_all, + "pengampu_all": pengampu_all, + "ruangan_all": ruangan_all, + "hari_all": hari_all, + "jam_all": jam_all, + "kelas_all": kelas_all, + }) + +def buat_jadwal(request): + if request.method == "POST": + pengampu_all = DosenPengampu.objects.all() + ruangan_all = Ruangan.objects.all() + hari_all = Hari.objects.all() + jam_all = Jam.objects.all() + + if not (pengampu_all and ruangan_all and hari_all and jam_all): + messages.error(request, "Data Master belum lengkap.") + return redirect("buat_jadwal") + + # Only delete schedules that were not manually created + Jadwal.objects.filter(is_manual=False).delete() + + # Get pengampu that don't have a schedule yet (including manual ones) + pengampu_with_jadwal = Jadwal.objects.values_list('dosen_pengampu_id', flat=True) + pending_pengampu = pengampu_all.exclude(id__in=pengampu_with_jadwal) + + for pengampu in pending_pengampu: + # We use the class assigned in DosenPengampu + kelas = pengampu.kelas + if not kelas: + # Fallback to random class if not assigned in Pengampu (for legacy data) + kelas_all = Kelas.objects.all() + kelas = random.choice(kelas_all) if kelas_all.exists() else None + + if not kelas: continue + + # Simple logic to find an available slot (avoiding collisions for manual ones) + # This is still a basic random generator but it now respects existing manual entries + max_tries = 50 + for _ in range(max_tries): + h = random.choice(hari_all) + j = random.choice(jam_all) + r = random.choice(ruangan_all) + + # Check for collisions in that room/time + if not Jadwal.objects.filter(hari=h, jam=j, ruangan=r).exists() and \ + not Jadwal.objects.filter(hari=h, jam=j, kelas=kelas).exists() and \ + not Jadwal.objects.filter(hari=h, jam=j, dosen_pengampu__dosen=pengampu.dosen).exists(): + + Jadwal.objects.create( + dosen_pengampu=pengampu, + ruangan=r, + hari=h, + jam=j, + kelas=kelas, + is_manual=False + ) + break + + messages.success(request, "Jadwal berhasil digenerate otomatis (menghindari jadwal manual).") + return redirect("jadwal_list") + return render(request, "core/buat_jadwal.html") + +def jadwal_delete(request, pk): + get_object_or_404(Jadwal, pk=pk).delete() + messages.success(request, "Jadwal berhasil dihapus.") + return redirect("jadwal_list") + +def jadwal_export_excel(request): + wb = Workbook() + ws = wb.active + ws.title = "Jadwal Kuliah" + + headers = [ + 'No', 'Hari', 'Jam', 'Kode MK', 'Mata Kuliah', 'SKS', 'Semester', + 'Dosen', 'Kelas', 'Ruangan', 'Program Studi', 'Tahun Akademik' + ] + ws.append(headers) + + jadwal_all = Jadwal.objects.all().select_related( + 'hari', 'jam', 'dosen_pengampu__matkul', 'dosen_pengampu__dosen', + 'kelas', 'ruangan', 'dosen_pengampu__matkul__prodi', 'dosen_pengampu__tahun_akademik' + ).order_by('hari', 'jam') + + for i, j in enumerate(jadwal_all, 1): + ws.append([ + i, + j.hari.nama, + j.jam.range_waktu, + j.dosen_pengampu.matkul.kode, + j.dosen_pengampu.matkul.nama, + j.dosen_pengampu.matkul.sks, + j.dosen_pengampu.matkul.semester, + j.dosen_pengampu.dosen.nama, + j.kelas.nama, + j.ruangan.nama, + j.dosen_pengampu.matkul.prodi.nama, + j.dosen_pengampu.tahun_akademik.nama + ]) + + response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + response['Content-Disposition'] = 'attachment; filename="jadwal_kuliah.xlsx"' + wb.save(response) + return response diff --git a/requirements.txt b/requirements.txt index e22994c..70ec869 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ Django==5.2.7 mysqlclient==2.2.7 python-dotenv==1.1.1 +openpyxl==3.1.5 diff --git a/static/css/custom.css b/static/css/custom.css index 925f6ed..78856cd 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -1,4 +1,152 @@ -/* 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;700&family=Outfit:wght@400;600;700&display=swap'); + +:root { + --primary-color: #00B4D8; + --secondary-color: #FFB703; + --dark-bg: #212529; + --light-bg: #F8F9FA; + --sidebar-width: 280px; + --text-primary: #333; + --text-muted: #6c757d; } + +body { + font-family: 'Inter', sans-serif; + background-color: var(--light-bg); + color: var(--text-primary); + overflow-x: hidden; +} + +h1, h2, h3, h4, h5, h6 { + font-family: 'Outfit', sans-serif; + font-weight: 600; +} + +/* Sidebar */ +#sidebar { + width: var(--sidebar-width); + height: 100vh; + position: fixed; + top: 0; + left: 0; + background-color: var(--dark-bg); + color: white; + z-index: 1000; + transition: all 0.3s; + padding-top: 20px; +} + +#sidebar .nav-link { + color: rgba(255,255,255,0.7); + padding: 12px 25px; + display: flex; + align-items: center; + border-radius: 0; + transition: 0.2s; +} + +#sidebar .nav-link i { + margin-right: 15px; + font-size: 1.1rem; +} + +#sidebar .nav-link:hover, #sidebar .nav-link.active { + color: white; + background-color: rgba(255,255,255,0.1); + border-left: 4px solid var(--primary-color); +} + +#sidebar .sidebar-header { + padding: 20px 25px; + margin-bottom: 20px; +} + +/* Main Content */ +#main-content { + margin-left: var(--sidebar-width); + padding: 30px; + min-height: 100vh; +} + +.top-navbar { + background: white; + padding: 15px 30px; + border-radius: 15px; + margin-bottom: 30px; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0 4px 6px rgba(0,0,0,0.02); +} + +/* Cards */ +.card { + border: none; + border-radius: 15px; + box-shadow: 0 4px 15px rgba(0,0,0,0.05); + transition: transform 0.3s; +} + +.card:hover { + transform: translateY(-5px); +} + +.card-stat { + padding: 25px; +} + +.card-stat i { + font-size: 2.5rem; + color: var(--primary-color); + margin-bottom: 15px; +} + +/* Buttons */ +.btn-primary { + background-color: var(--primary-color); + border: none; + border-radius: 10px; + padding: 10px 20px; + font-weight: 500; +} + +.btn-primary:hover { + background-color: #0096B4; +} + +/* Tables */ +.table { + background: white; + border-radius: 15px; + overflow: hidden; +} + +.table thead th { + background-color: #f1f3f5; + border: none; + padding: 15px; + font-weight: 600; +} + +.table tbody td { + padding: 15px; + vertical-align: middle; +} + +/* Profile Icon */ +.profile-section { + display: flex; + align-items: center; +} + +.profile-img { + width: 40px; + height: 40px; + border-radius: 50%; + background-color: var(--primary-color); + display: flex; + align-items: center; + justify-content: center; + color: white; + margin-right: 10px; +} \ No newline at end of file