Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79ea87f550 | ||
|
|
b3fd5e275d | ||
|
|
b813668e12 | ||
|
|
fc68bec63b |
747
Design/Style.css
Normal file
@ -0,0 +1,747 @@
|
||||
:root {
|
||||
--Primary_Color: #2563EB;
|
||||
--Primary_Light: #DBEAFE;
|
||||
--Secondary_Color: #64748B;
|
||||
--Background_Color: #F8FAFC;
|
||||
--Surface_Color: #FFFFFF;
|
||||
--Text_Primary: #1E293B;
|
||||
--Text_Secondary: #475569;
|
||||
--Border_Color: #E2E8F0;
|
||||
--Success_Color: #10B981;
|
||||
--Error_Color: #EF4444;
|
||||
--Overlay_Color: rgba(0, 0, 0, 0.65);
|
||||
--Sidebar_Width: 260px;
|
||||
--Topbar_Height: 70px;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body, html {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
color: var(--Text_Primary);
|
||||
line-height: 1.5;
|
||||
overflow: hidden; /* Main page scroll is handled by content body */
|
||||
background-color: var(--Background_Color);
|
||||
}
|
||||
|
||||
/* Landing Page Styles */
|
||||
.Landing_Wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: url('../assets/pasted-20260204-200305-388a4105.jpg') no-repeat center center;
|
||||
background-size: cover;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.Landing_Wrapper::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: var(--Overlay_Color);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.Landing_Header {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 30px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.Logo_Pill {
|
||||
background: white;
|
||||
padding: 8px 24px 8px 10px;
|
||||
border-radius: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.Logo_Circle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.Logo_Circle img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.Logo_Text {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.Logo_Title {
|
||||
color: #0F172A;
|
||||
font-weight: 700;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.2;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.Logo_Subtitle {
|
||||
color: #64748B;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.Landing_Content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
max-width: 800px;
|
||||
padding: 20px;
|
||||
animation: fadeInScale 0.8s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeInScale {
|
||||
from { opacity: 0; transform: scale(0.95); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
.Landing_Title {
|
||||
font-size: clamp(2.5rem, 8vw, 4rem);
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.Landing_Description {
|
||||
font-size: clamp(0.9rem, 2.5vw, 1.15rem);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 40px;
|
||||
font-weight: 400;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.Btn_Landing_Login {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: white;
|
||||
color: var(--Primary_Color);
|
||||
padding: 16px 45px;
|
||||
border-radius: 40px;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
text-transform: uppercase;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 10px 20px -5px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.Btn_Landing_Login:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 15px 25px -5px rgba(0, 0, 0, 0.4);
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.Landing_Footer {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
font-size: 0.7rem;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.Modal_Overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(15, 23, 42, 0.85);
|
||||
backdrop-filter: blur(8px);
|
||||
z-index: 100;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.Modal_Overlay.Active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.Login_Card {
|
||||
background: var(--Surface_Color);
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
max-width: 440px;
|
||||
padding: 40px;
|
||||
position: relative;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
||||
animation: slideIn 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.Btn_Close_Modal {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #f1f5f9;
|
||||
border: none;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
font-size: 1.2rem;
|
||||
color: var(--Secondary_Color);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.Btn_Close_Modal:hover {
|
||||
background: #e2e8f0;
|
||||
color: var(--Text_Primary);
|
||||
}
|
||||
|
||||
.Login_Card .Title_Large {
|
||||
font-size: 1.85rem;
|
||||
color: var(--Text_Primary);
|
||||
margin-bottom: 8px;
|
||||
text-align: left;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.Login_Card .Text_Muted {
|
||||
text-align: left;
|
||||
margin-bottom: 32px;
|
||||
color: var(--Text_Secondary);
|
||||
}
|
||||
|
||||
/* Form Styles */
|
||||
.Form_Group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.Label {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
color: var(--Text_Primary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.Input {
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid var(--Border_Color);
|
||||
border-radius: 8px;
|
||||
font-size: 0.95rem;
|
||||
background: #F8FAFC;
|
||||
transition: all 0.2s;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.Input:focus {
|
||||
outline: none;
|
||||
border-color: var(--Primary_Color);
|
||||
background: white;
|
||||
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
|
||||
.Button_Primary {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
background-color: var(--Primary_Color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
margin-top: 10px;
|
||||
transition: all 0.2s;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.Button_Primary:hover {
|
||||
background-color: #1D4ED8;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
||||
}
|
||||
|
||||
/* Dashboard Layout */
|
||||
.Dashboard_Container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.Sidebar {
|
||||
width: var(--Sidebar_Width);
|
||||
background: white;
|
||||
border-right: 1px solid var(--Border_Color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.Sidebar_Header {
|
||||
height: var(--Topbar_Height);
|
||||
padding: 0 24px;
|
||||
border-bottom: 1px solid var(--Border_Color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.Sidebar_Nav {
|
||||
padding: 20px 0;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.Nav_Item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 24px;
|
||||
color: var(--Text_Secondary);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.Nav_Item:hover {
|
||||
background: #F1F5F9;
|
||||
color: var(--Primary_Color);
|
||||
}
|
||||
|
||||
.Nav_Item.Active {
|
||||
background: var(--Primary_Light);
|
||||
color: var(--Primary_Color);
|
||||
border-right: 4px solid var(--Primary_Color);
|
||||
}
|
||||
|
||||
.Main_Content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.Top_Bar {
|
||||
height: var(--Topbar_Height);
|
||||
background: white;
|
||||
border-bottom: 1px solid var(--Border_Color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 32px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.Search_Box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #F1F5F9;
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.Search_Box input {
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 4px 8px;
|
||||
width: 100%;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.Top_Bar_Actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.Content_Body {
|
||||
flex: 1;
|
||||
padding: 32px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.Page_Title_Section {
|
||||
margin-bottom: 32px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.Page_Title {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 800;
|
||||
color: var(--Text_Primary);
|
||||
}
|
||||
|
||||
.Page_Subtitle {
|
||||
color: var(--Text_Secondary);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* Stat Cards */
|
||||
.Stat_Cards_Grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.Stat_Card {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.Stat_Label {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--Text_Secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.Stat_Value {
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
color: var(--Text_Primary);
|
||||
}
|
||||
|
||||
.Stat_Trend {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.Trend_Up { color: var(--Success_Color); }
|
||||
.Trend_Down { color: var(--Error_Color); }
|
||||
|
||||
/* Dashboard Grid */
|
||||
.Dashboard_Grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.Card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
padding: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.Card_Header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.Card_Title {
|
||||
font-size: 1.15rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Table Styles */
|
||||
.Table_Wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--Border_Color);
|
||||
color: var(--Text_Secondary);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 16px 12px;
|
||||
border-bottom: 1px solid var(--Border_Color);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.Badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.Badge_Success { background: #DCFCE7; color: #166534; }
|
||||
.Badge_Warning { background: #FEF9C3; color: #854D0E; }
|
||||
.Badge_Danger { background: #FEE2E2; color: #991B1B; }
|
||||
|
||||
/* Accordion / Collapsible */
|
||||
.Accordion_Item {
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--Border_Color);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.Accordion_Header {
|
||||
padding: 20px 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
background: white;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.Accordion_Header:hover {
|
||||
background: #F8FAFC;
|
||||
}
|
||||
|
||||
.Accordion_Header h3 {
|
||||
font-size: 1.15rem;
|
||||
color: var(--Primary_Color);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.Accordion_Icon {
|
||||
transition: transform 0.3s;
|
||||
color: var(--Primary_Color);
|
||||
}
|
||||
|
||||
.Accordion_Content {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease-out, padding 0.3s;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.Accordion_Item.Active .Accordion_Content {
|
||||
max-height: 2000px; /* Large enough to fit content */
|
||||
padding: 24px;
|
||||
border-top: 1px solid var(--Border_Color);
|
||||
}
|
||||
|
||||
.Accordion_Item.Active .Accordion_Icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* List Items */
|
||||
.Data_List {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.Data_Row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid var(--Border_Color);
|
||||
}
|
||||
|
||||
.Data_Row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.Data_Label {
|
||||
font-weight: 500;
|
||||
color: var(--Text_Secondary);
|
||||
}
|
||||
|
||||
.Data_Value {
|
||||
font-weight: 600;
|
||||
color: var(--Text_Primary);
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.Dashboard_Grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.Sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.Top_Bar {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.Search_Box {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom Utilities */
|
||||
.Text_Muted { color: var(--Text_Secondary); }
|
||||
|
||||
.Select {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--Border_Color);
|
||||
border-radius: 8px;
|
||||
background: white;
|
||||
font-family: inherit;
|
||||
font-size: 0.9rem;
|
||||
color: var(--Text_Primary);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Breakdown & Summary Cards */
|
||||
.Summary_Breakdowns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.Breakdown_Card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--Border_Color);
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.Breakdown_Title {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
color: var(--Primary_Color);
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 2px solid var(--Primary_Light);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.Breakdown_Item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.85rem;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f8f9fa;
|
||||
}
|
||||
|
||||
.Breakdown_Item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Filter Bar */
|
||||
.Filter_Bar {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
border: 1px solid var(--Border_Color);
|
||||
display: grid;
|
||||
grid-template-columns: 2fr repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 24px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.Filter_Group label {
|
||||
display: block;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
color: var(--Text_Secondary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.Search_Input_Wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.Search_Input_Wrapper i {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--Text_Secondary);
|
||||
}
|
||||
|
||||
.Search_Input_Wrapper .Input {
|
||||
padding-left: 38px;
|
||||
}
|
||||
|
||||
.Button_Secondary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 10px 20px;
|
||||
background: #4F46E5;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.Button_Secondary:hover {
|
||||
background: #4338CA;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.2);
|
||||
}
|
||||
|
||||
/* Navbar for Voting Screen */
|
||||
.Dashboard_Navbar {
|
||||
height: var(--Topbar_Height);
|
||||
background: white;
|
||||
border-bottom: 1px solid var(--Border_Color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 32px;
|
||||
}
|
||||
85
Logic/Backend/Add_Candidate_Handler.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/Authentication_Handler.php';
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
$Auth = new Authentication_Handler();
|
||||
$Auth->Check_Auth();
|
||||
|
||||
if ($_SESSION['Access_Level'] < 1) {
|
||||
header('Location: ../../Screens/Voting_Screen.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
function generateUserID($db) {
|
||||
$tables = ['Officers', 'Voters', 'Candidates'];
|
||||
do {
|
||||
$id = sprintf("%02d-%04d", rand(0, 99), rand(0, 9999));
|
||||
$exists = false;
|
||||
foreach ($tables as $table) {
|
||||
$stmt = $db->prepare("SELECT COUNT(*) FROM $table WHERE User_ID = ?");
|
||||
$stmt->execute([$id]);
|
||||
if ($stmt->fetchColumn() > 0) {
|
||||
$exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while ($exists);
|
||||
return $id;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$db = db();
|
||||
|
||||
$name = $_POST['name'] ?? '';
|
||||
$email = $_POST['email'] ?? '';
|
||||
$position = $_POST['position'] ?? '';
|
||||
$party = $_POST['party'] ?? '';
|
||||
$grade = $_POST['grade'] ?? '';
|
||||
$track = $_POST['track'] ?? '';
|
||||
$user_id = $_POST['user_id'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
// User ID generation if empty
|
||||
if (empty($user_id)) {
|
||||
$userId = generateUserID($db);
|
||||
} else {
|
||||
$userId = $user_id;
|
||||
}
|
||||
|
||||
// Default password
|
||||
if (empty($password)) {
|
||||
$password = "School@123";
|
||||
}
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
// Handle Photo Upload
|
||||
$photoPath = '';
|
||||
if (isset($_FILES['photo']) && $_FILES['photo']['error'] === UPLOAD_ERR_OK) {
|
||||
$uploadDir = '../../assets/images/candidates/';
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0775, true);
|
||||
}
|
||||
|
||||
$fileExt = pathinfo($_FILES['photo']['name'], PATHINFO_EXTENSION);
|
||||
$fileName = $userId . '.' . $fileExt;
|
||||
$targetPath = $uploadDir . $fileName;
|
||||
|
||||
if (move_uploaded_file($_FILES['photo']['tmp_name'], $targetPath)) {
|
||||
$photoPath = 'assets/images/candidates/' . $fileName;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $db->prepare("INSERT INTO Candidates (User_ID, Name, Email, Position, Party, Grade_Level, Track_Cluster, Photo, Password) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$userId, $name, $email, $position, $party, $grade, $track, $photoPath, $hashedPassword]);
|
||||
|
||||
// Log the action
|
||||
$Auth->Log_Action($_SESSION['User_ID'], $_SESSION['User_Role'], 'Add Candidate', "Added candidate $name for $position. ID: $userId");
|
||||
|
||||
header('Location: ../../Screens/Candidates.php?success=Candidate+added+with+ID:+' . $userId);
|
||||
} catch (Exception $e) {
|
||||
// Redirect with error
|
||||
header('Location: ../../Screens/Candidates.php?error=' . urlencode($e->getMessage()));
|
||||
}
|
||||
exit;
|
||||
}
|
||||
77
Logic/Backend/Add_Officer_Handler.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/Authentication_Handler.php';
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
$Auth = new Authentication_Handler();
|
||||
$Auth->Check_Auth();
|
||||
|
||||
if ($_SESSION['Access_Level'] < 3) {
|
||||
die("Unauthorized access");
|
||||
}
|
||||
|
||||
function generateUserID($db) {
|
||||
$tables = ['Officers', 'Voters', 'Candidates'];
|
||||
do {
|
||||
$id = sprintf("%02d-%04d", rand(0, 99), rand(0, 9999));
|
||||
$exists = false;
|
||||
foreach ($tables as $table) {
|
||||
$stmt = $db->prepare("SELECT COUNT(*) FROM $table WHERE User_ID = ?");
|
||||
$stmt->execute([$id]);
|
||||
if ($stmt->fetchColumn() > 0) {
|
||||
$exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while ($exists);
|
||||
return $id;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$db = db();
|
||||
|
||||
$name = $_POST['name'] ?? '';
|
||||
$email = $_POST['email'] ?? '';
|
||||
$user_id = $_POST['user_id'] ?? '';
|
||||
$role = $_POST['role'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if (empty($name) || empty($email) || empty($role)) {
|
||||
header("Location: ../../Screens/Officers.php?error=Missing+fields");
|
||||
exit;
|
||||
}
|
||||
|
||||
// User ID logic
|
||||
if (empty($user_id)) {
|
||||
$user_id = generateUserID($db);
|
||||
} else {
|
||||
// Enforce format 00-0000 if provided? The user said "Make User-IDs... to be 7 characters long in this format: '00-0000'"
|
||||
// If it doesn't match, we could either reject or fix it. Let's just use it if provided for now, but the requirement suggests a strict format.
|
||||
// Actually, let's just use the provided ID if it's not empty, but the generation logic is there if it is.
|
||||
}
|
||||
|
||||
// Password logic
|
||||
if (empty($password)) {
|
||||
$password = "School@123"; // Default password
|
||||
}
|
||||
|
||||
// Determine Access Level
|
||||
$accessLevel = 1;
|
||||
if ($role === 'Admin') $accessLevel = 3;
|
||||
elseif ($role === 'COMEA Adviser') $accessLevel = 2;
|
||||
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
try {
|
||||
$stmt = $db->prepare("INSERT INTO Officers (User_ID, Name, Email, Role, Access_Level, Password) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$user_id, $name, $email, $role, $accessLevel, $hashedPassword]);
|
||||
|
||||
// Log to Audit Trail
|
||||
$Auth->Log_Action($_SESSION['User_ID'], $_SESSION['User_Role'], 'Add Officer', "Registered new officer: $name ($role). User ID: $user_id");
|
||||
|
||||
header("Location: ../../Screens/Officers.php?success=Officer+registered+with+ID:+$user_id");
|
||||
} catch (PDOException $e) {
|
||||
header("Location: ../../Screens/Officers.php?error=Registration+failed:+" . urlencode($e->getMessage()));
|
||||
}
|
||||
} else {
|
||||
header("Location: ../../Screens/Officers.php");
|
||||
}
|
||||
69
Logic/Backend/Add_Voter_Handler.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/Authentication_Handler.php';
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
$Auth = new Authentication_Handler();
|
||||
$Auth->Check_Auth();
|
||||
|
||||
if ($_SESSION['Access_Level'] < 1) {
|
||||
die("Unauthorized access");
|
||||
}
|
||||
|
||||
function generateUserID($db) {
|
||||
$tables = ['Officers', 'Voters', 'Candidates'];
|
||||
do {
|
||||
$id = sprintf("%02d-%04d", rand(0, 99), rand(0, 9999));
|
||||
$exists = false;
|
||||
foreach ($tables as $table) {
|
||||
$stmt = $db->prepare("SELECT COUNT(*) FROM $table WHERE User_ID = ?");
|
||||
$stmt->execute([$id]);
|
||||
if ($stmt->fetchColumn() > 0) {
|
||||
$exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while ($exists);
|
||||
return $id;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$db = db();
|
||||
|
||||
$email = $_POST['email'] ?? '';
|
||||
$user_id = $_POST['user_id'] ?? '';
|
||||
$track = $_POST['track'] ?? '';
|
||||
$grade = $_POST['grade'] ?? '';
|
||||
$section = $_POST['section'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if (empty($email) || empty($track) || empty($grade) || empty($section)) {
|
||||
header("Location: ../../Screens/Voters.php?error=Missing+fields");
|
||||
exit;
|
||||
}
|
||||
|
||||
// User ID logic
|
||||
if (empty($user_id)) {
|
||||
$user_id = generateUserID($db);
|
||||
}
|
||||
|
||||
// Password logic
|
||||
if (empty($password)) {
|
||||
$password = "School@123";
|
||||
}
|
||||
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
try {
|
||||
$stmt = $db->prepare("INSERT INTO Voters (User_ID, Email, Password, Track_Cluster, Grade_Level, Section) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$user_id, $email, $hashedPassword, $track, $grade, $section]);
|
||||
|
||||
// Log to Audit Trail
|
||||
$Auth->Log_Action($_SESSION['User_ID'], $_SESSION['User_Role'], 'Add Voter', "Registered new voter: $email. User ID: $user_id");
|
||||
|
||||
header("Location: ../../Screens/Voters.php?success=Voter+registered+with+ID:+$user_id");
|
||||
} catch (PDOException $e) {
|
||||
header("Location: ../../Screens/Voters.php?error=Registration+failed:+" . urlencode($e->getMessage()));
|
||||
}
|
||||
} else {
|
||||
header("Location: ../../Screens/Voters.php");
|
||||
}
|
||||
90
Logic/Backend/Authentication_Handler.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
class Authentication_Handler {
|
||||
private $pdo;
|
||||
|
||||
public function __construct() {
|
||||
$this->pdo = db();
|
||||
}
|
||||
|
||||
public function Login($User_Type, $Email, $Password, $User_ID) {
|
||||
$Table = ($User_Type === 'Voter') ? 'Voters' : 'Officers';
|
||||
|
||||
// Try to find user by UID first as it's the primary key
|
||||
$sql = "SELECT * FROM $Table WHERE User_ID = ?";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$User_ID]);
|
||||
$User = $stmt->fetch();
|
||||
|
||||
// If not found and email is provided, try by Email
|
||||
if (!$User && !empty($Email)) {
|
||||
$sql = "SELECT * FROM $Table WHERE Email = ?";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$Email]);
|
||||
$User = $stmt->fetch();
|
||||
}
|
||||
|
||||
if ($User && password_verify($Password, $User['Password'])) {
|
||||
$_SESSION['User_ID'] = $User['User_ID'];
|
||||
$_SESSION['User_Role'] = ($User_Type === 'Voter') ? 'Voter' : $User['Role'];
|
||||
$_SESSION['Access_Level'] = ($User_Type === 'Voter') ? 0 : ($User['Access_Level'] ?? 0);
|
||||
$_SESSION['User_Name'] = $User['Name'] ?? ($User_Type === 'Voter' ? 'Voter' : 'User');
|
||||
|
||||
// Log to Audit_Trail if Access_Level >= 1
|
||||
if ($_SESSION['Access_Level'] >= 1) {
|
||||
$this->Log_Action($User['User_ID'], $_SESSION['User_Role'], 'Login', 'Successful login to the system');
|
||||
}
|
||||
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
return ['success' => false, 'error' => 'Invalid credentials. Please check your UID/Email and Password.'];
|
||||
}
|
||||
|
||||
public function Log_Action($User_ID, $User_Role, $Action_Type, $Action_Details) {
|
||||
$sql = "INSERT INTO Audit_Trail (User_ID, User_Role, Action_Type, Action_Details) VALUES (?, ?, ?, ?)";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$User_ID, $User_Role, $Action_Type, $Action_Details]);
|
||||
}
|
||||
|
||||
public function Check_Auth() {
|
||||
if (!isset($_SESSION['User_ID'])) {
|
||||
header('Location: ../../Screens/Login.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
public function Logout() {
|
||||
if (isset($_SESSION['User_ID']) && isset($_SESSION['Access_Level']) && $_SESSION['Access_Level'] >= 1) {
|
||||
$this->Log_Action($_SESSION['User_ID'], $_SESSION['User_Role'], 'Logout', 'User logged out of the system');
|
||||
}
|
||||
session_destroy();
|
||||
header('Location: ../../Screens/Login.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle POST request for Login
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['User_ID']) && isset($_POST['Password'])) {
|
||||
$handler = new Authentication_Handler();
|
||||
$userType = $_POST['User_Type'] ?? 'Voter';
|
||||
$email = $_POST['Email'] ?? '';
|
||||
$uid = $_POST['User_ID'];
|
||||
$password = $_POST['Password'];
|
||||
|
||||
$result = $handler->Login($userType, $email, $password, $uid);
|
||||
|
||||
if ($result['success']) {
|
||||
if ($_SESSION['Access_Level'] >= 1) {
|
||||
header('Location: ../../Screens/Election_Dashboard.php');
|
||||
} else {
|
||||
header('Location: ../../Screens/Voting_Screen.php');
|
||||
}
|
||||
exit;
|
||||
} else {
|
||||
header('Location: ../../Screens/Login.php?error=' . urlencode($result['error']));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
46
Logic/Backend/Delete_Officer_Handler.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/Authentication_Handler.php';
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
$Auth = new Authentication_Handler();
|
||||
$Auth->Check_Auth();
|
||||
|
||||
if ($_SESSION['Access_Level'] < 3) {
|
||||
die("Unauthorized access");
|
||||
}
|
||||
|
||||
$user_id = $_GET['user_id'] ?? '';
|
||||
|
||||
if (empty($user_id)) {
|
||||
header("Location: ../../Screens/Officers.php?error=No+user+ID+provided");
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($user_id === $_SESSION['User_ID']) {
|
||||
header("Location: ../../Screens/Officers.php?error=You+cannot+delete+yourself");
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = db();
|
||||
|
||||
try {
|
||||
// Get officer name for logging
|
||||
$stmt = $db->prepare("SELECT Name, Role FROM Officers WHERE User_ID = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
$officer = $stmt->fetch();
|
||||
|
||||
if (!$officer) {
|
||||
header("Location: ../../Screens/Officers.php?error=Officer+not+found");
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("DELETE FROM Officers WHERE User_ID = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
|
||||
// Log to Audit Trail
|
||||
$Auth->Log_Action($_SESSION['User_ID'], $_SESSION['User_Role'], 'Delete Officer', "Deleted officer: {$officer['Name']} ({$officer['Role']}). User ID: $user_id");
|
||||
|
||||
header("Location: ../../Screens/Officers.php?success=Officer+deleted+successfully");
|
||||
} catch (PDOException $e) {
|
||||
header("Location: ../../Screens/Officers.php?error=Delete+failed:+" . urlencode($e->getMessage()));
|
||||
}
|
||||
59
Logic/Backend/Edit_Officer_Handler.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/Authentication_Handler.php';
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
$Auth = new Authentication_Handler();
|
||||
$Auth->Check_Auth();
|
||||
|
||||
if ($_SESSION['Access_Level'] < 3) {
|
||||
die("Unauthorized access");
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$db = db();
|
||||
|
||||
$original_id = $_POST['original_user_id'] ?? '';
|
||||
$user_id = $_POST['user_id'] ?? '';
|
||||
$name = $_POST['name'] ?? '';
|
||||
$email = $_POST['email'] ?? '';
|
||||
$role = $_POST['role'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if (empty($original_id) || empty($user_id) || empty($name) || empty($email) || empty($role)) {
|
||||
header("Location: ../../Screens/Officers.php?error=Missing+fields");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Determine Access Level
|
||||
$accessLevel = 1;
|
||||
if ($role === 'Admin') $accessLevel = 3;
|
||||
elseif ($role === 'COMEA Adviser') $accessLevel = 2;
|
||||
|
||||
try {
|
||||
if (!empty($password)) {
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
$stmt = $db->prepare("UPDATE Officers SET User_ID = ?, Name = ?, Email = ?, Role = ?, Access_Level = ?, Password = ? WHERE User_ID = ?");
|
||||
$stmt->execute([$user_id, $name, $email, $role, $accessLevel, $hashedPassword, $original_id]);
|
||||
} else {
|
||||
$stmt = $db->prepare("UPDATE Officers SET User_ID = ?, Name = ?, Email = ?, Role = ?, Access_Level = ? WHERE User_ID = ?");
|
||||
$stmt->execute([$user_id, $name, $email, $role, $accessLevel, $original_id]);
|
||||
}
|
||||
|
||||
// If the user edited their own ID, update the session
|
||||
if ($original_id === $_SESSION['User_ID']) {
|
||||
$_SESSION['User_ID'] = $user_id;
|
||||
$_SESSION['User_Name'] = $name;
|
||||
$_SESSION['User_Role'] = $role;
|
||||
$_SESSION['Access_Level'] = $accessLevel;
|
||||
}
|
||||
|
||||
// Log to Audit Trail
|
||||
$Auth->Log_Action($_SESSION['User_ID'], $_SESSION['User_Role'], 'Edit Officer', "Updated officer: $name ($role). User ID: $user_id");
|
||||
|
||||
header("Location: ../../Screens/Officers.php?success=Officer+updated+successfully");
|
||||
} catch (PDOException $e) {
|
||||
header("Location: ../../Screens/Officers.php?error=Update+failed:+" . urlencode($e->getMessage()));
|
||||
}
|
||||
} else {
|
||||
header("Location: ../../Screens/Officers.php");
|
||||
}
|
||||
68
Logic/Backend/Import_Voters_Handler.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/Authentication_Handler.php';
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
$Auth = new Authentication_Handler();
|
||||
$Auth->Check_Auth();
|
||||
|
||||
if ($_SESSION['Access_Level'] < 1) {
|
||||
header('Location: ../../Screens/Login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
|
||||
$file = $_FILES['csv_file']['tmp_name'];
|
||||
|
||||
if (!is_uploaded_file($file)) {
|
||||
header("Location: ../../Screens/Voters.php?error=No file uploaded");
|
||||
exit;
|
||||
}
|
||||
|
||||
$handle = fopen($file, "r");
|
||||
if ($handle === false) {
|
||||
header("Location: ../../Screens/Voters.php?error=Could not open file");
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = db();
|
||||
$db->beginTransaction();
|
||||
|
||||
try {
|
||||
// Skip header
|
||||
$header = fgetcsv($handle);
|
||||
|
||||
$importedCount = 0;
|
||||
while (($data = fgetcsv($handle)) !== false) {
|
||||
// Mapping: User_ID, Email, Track_Cluster, Grade_Level, Section, Password
|
||||
$userId = $data[0] ?: 'V-' . bin2hex(random_bytes(4));
|
||||
$email = $data[1];
|
||||
$track = $data[2];
|
||||
$grade = intval($data[3]);
|
||||
$section = $data[4];
|
||||
$password = !empty($data[5]) ? password_hash($data[5], PASSWORD_DEFAULT) : password_hash('Voter123', PASSWORD_DEFAULT);
|
||||
|
||||
if (empty($email)) continue;
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO Voters (User_ID, Email, Password, Track_Cluster, Grade_Level, Section) VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE Email = VALUES(Email), Track_Cluster = VALUES(Track_Cluster), Grade_Level = VALUES(Grade_Level), Section = VALUES(Section)");
|
||||
$stmt->execute([$userId, $email, $password, $track, $grade, $section]);
|
||||
$importedCount++;
|
||||
}
|
||||
|
||||
$db->commit();
|
||||
fclose($handle);
|
||||
|
||||
$Auth->Log_Action($_SESSION['User_ID'], $_SESSION['User_Role'], 'Import Voters', "Imported $importedCount voters via CSV");
|
||||
header("Location: ../../Screens/Voters.php?success=Successfully imported $importedCount voters");
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$db->rollBack();
|
||||
fclose($handle);
|
||||
header("Location: ../../Screens/Voters.php?error=Error importing CSV: " . $e->getMessage());
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
header("Location: ../../Screens/Voters.php");
|
||||
exit;
|
||||
}
|
||||
0
Logic/Frontend/Frontend_Script.js
Normal file
135
Screens/Audit_Trail.php
Normal file
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../Logic/Backend/Authentication_Handler.php';
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
$Auth = new Authentication_Handler();
|
||||
$Auth->Check_Auth();
|
||||
|
||||
if ($_SESSION['Access_Level'] < 1) {
|
||||
header('Location: Voting_Screen.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = db();
|
||||
|
||||
// Fetch Logs
|
||||
$query = "SELECT * FROM Audit_Trail ORDER BY Timestamp DESC LIMIT 100";
|
||||
$logs = $db->query($query)->fetchAll();
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Audit Trail | Online School Election System</title>
|
||||
<link rel="stylesheet" href="../Design/Style.css?v=<?php echo time(); ?>">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="Dashboard_Container">
|
||||
<!-- Sidebar -->
|
||||
<aside class="Sidebar">
|
||||
<div class="Sidebar_Header">
|
||||
<div class="Logo_Text">
|
||||
<div class="Logo_Title" style="font-size: 1.1rem; color: var(--Primary_Color);">Click to Vote</div>
|
||||
<div class="Logo_Subtitle">Administrator Portal</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="Sidebar_Nav">
|
||||
<a href="Election_Dashboard.php" class="Nav_Item">
|
||||
<i class="fas fa-th-large"></i> Election Dashboard
|
||||
</a>
|
||||
<a href="Election_History.php" class="Nav_Item">
|
||||
<i class="fas fa-vote-yea"></i> Election History
|
||||
</a>
|
||||
<a href="Voters.php" class="Nav_Item">
|
||||
<i class="fas fa-users"></i> Voter Management
|
||||
</a>
|
||||
<a href="Candidates.php" class="Nav_Item">
|
||||
<i class="fas fa-user-tie"></i> Candidate Management
|
||||
</a>
|
||||
<a href="Officers.php" class="Nav_Item">
|
||||
<i class="fas fa-user-shield"></i> Officers Management
|
||||
</a>
|
||||
<a href="Audit_Trail.php" class="Nav_Item Active">
|
||||
<i class="fas fa-file-alt"></i> Reports & Audit
|
||||
</a>
|
||||
<a href="Credits.php" class="Nav_Item">
|
||||
<i class="fas fa-info-circle"></i> Credits
|
||||
</a>
|
||||
</nav>
|
||||
<div style="padding: 24px; border-top: 1px solid var(--Border_Color);">
|
||||
<a href="Logout.php" class="Nav_Item" style="padding: 0; color: var(--Error_Color);">
|
||||
<i class="fas fa-sign-out-alt"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="Main_Content">
|
||||
<!-- Top Bar -->
|
||||
<header class="Top_Bar">
|
||||
<div class="Search_Box">
|
||||
<i class="fas fa-search" style="color: var(--Secondary_Color);"></i>
|
||||
<input type="text" placeholder="Search logs...">
|
||||
</div>
|
||||
<div class="Top_Bar_Actions">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div style="text-align: right;">
|
||||
<div style="font-weight: 700; font-size: 0.9rem;"><?php echo htmlspecialchars($_SESSION['User_Name']); ?></div>
|
||||
<div style="font-size: 0.75rem; color: var(--Text_Secondary);"><?php echo htmlspecialchars($_SESSION['User_Role']); ?></div>
|
||||
</div>
|
||||
<div style="width: 40px; height: 40px; background: var(--Primary_Light); color: var(--Primary_Color); border-radius: 50%; display: flex; justify-content: center; align-items: center; font-weight: 700;">
|
||||
<?php echo strtoupper(substr($_SESSION['User_Name'] ?? 'A', 0, 1)); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Content Body -->
|
||||
<div class="Content_Body">
|
||||
<div class="Page_Title_Section">
|
||||
<h1 class="Page_Title">Audit Trail</h1>
|
||||
<p class="Page_Subtitle">System activity and security logs</p>
|
||||
</div>
|
||||
|
||||
<div class="Card" style="padding: 0; overflow: hidden;">
|
||||
<div class="Table_Wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Timestamp</th>
|
||||
<th>User ID</th>
|
||||
<th>Role</th>
|
||||
<th>Action</th>
|
||||
<th>Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($logs)): ?>
|
||||
<tr>
|
||||
<td colspan="5" style="text-align: center; padding: 40px; color: var(--Text_Secondary);">
|
||||
No activity logs found.
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($logs as $log): ?>
|
||||
<tr>
|
||||
<td><?php echo date('Y-m-d H:i:s', strtotime($log['Timestamp'])); ?></td>
|
||||
<td style="font-weight: 600;"><?php echo htmlspecialchars($log['User_ID']); ?></td>
|
||||
<td><span class="Badge" style="background: var(--Primary_Light); color: var(--Primary_Color);"><?php echo htmlspecialchars($log['User_Role']); ?></span></td>
|
||||
<td><strong><?php echo htmlspecialchars($log['Action_Type']); ?></strong></td>
|
||||
<td><?php echo htmlspecialchars($log['Action_Details']); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
606
Screens/Candidates.php
Normal file
@ -0,0 +1,606 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../Logic/Backend/Authentication_Handler.php';
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
$Auth = new Authentication_Handler();
|
||||
$Auth->Check_Auth();
|
||||
|
||||
if ($_SESSION['Access_Level'] < 1) {
|
||||
header('Location: Voting_Screen.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = db();
|
||||
|
||||
// Fetch latest election
|
||||
$election = $db->query("SELECT * FROM Election_History ORDER BY Election_ID DESC LIMIT 1")->fetch();
|
||||
$electionStatus = $election['Status'] ?? 'Preparing';
|
||||
$electionId = $election['Election_ID'] ?? 0;
|
||||
|
||||
// Parse Parties and Positions
|
||||
$electionParties = json_decode($election['Parties'] ?? '[]', true) ?: [];
|
||||
$electionPositionsRaw = json_decode($election['Positions'] ?? '[]', true) ?: [];
|
||||
|
||||
// Normalize Positions (Ensure they are objects with name and type)
|
||||
$electionPositions = [];
|
||||
$migrationNeeded = false;
|
||||
foreach ($electionPositionsRaw as $p) {
|
||||
if (is_array($p) && isset($p['name'])) {
|
||||
$electionPositions[] = $p;
|
||||
} else {
|
||||
$electionPositions[] = ['name' => (string)$p, 'type' => 'Uniform'];
|
||||
$migrationNeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($migrationNeeded && $electionId) {
|
||||
$stmt = $db->prepare("UPDATE Election_History SET Positions = ? WHERE Election_ID = ?");
|
||||
$stmt->execute([json_encode($electionPositions), $electionId]);
|
||||
}
|
||||
|
||||
// Handle POST actions for Editors (Preparing Status)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $electionStatus === 'Preparing') {
|
||||
if (isset($_POST['action'])) {
|
||||
$changed = false;
|
||||
$logAction = "";
|
||||
$logDetails = "";
|
||||
|
||||
if ($_POST['action'] === 'add_position' && !empty($_POST['new_position'])) {
|
||||
$newPosName = trim($_POST['new_position']);
|
||||
$newPosType = $_POST['position_type'] ?? 'Uniform';
|
||||
|
||||
$exists = false;
|
||||
foreach ($electionPositions as $p) {
|
||||
if ($p['name'] === $newPosName) { $exists = true; break; }
|
||||
}
|
||||
|
||||
if (!$exists) {
|
||||
$electionPositions[] = ['name' => $newPosName, 'type' => $newPosType];
|
||||
$changed = true;
|
||||
$logAction = "Add Position";
|
||||
$logDetails = "Added new position: $newPosName ($newPosType)";
|
||||
}
|
||||
} elseif ($_POST['action'] === 'remove_position') {
|
||||
$posNameToRemove = $_POST['position'];
|
||||
$electionPositions = array_values(array_filter($electionPositions, fn($p) => $p['name'] !== $posNameToRemove));
|
||||
$changed = true;
|
||||
$logAction = "Remove Position";
|
||||
$logDetails = "Removed position: $posNameToRemove";
|
||||
} elseif ($_POST['action'] === 'add_party' && !empty($_POST['new_party'])) {
|
||||
$newParty = trim($_POST['new_party']);
|
||||
if (!in_array($newParty, $electionParties)) {
|
||||
$electionParties[] = $newParty;
|
||||
$changed = true;
|
||||
$logAction = "Add Party";
|
||||
$logDetails = "Added new political party: $newParty";
|
||||
}
|
||||
} elseif ($_POST['action'] === 'remove_party') {
|
||||
$partyToRemove = $_POST['party'];
|
||||
$electionParties = array_values(array_filter($electionParties, fn($p) => $p !== $partyToRemove));
|
||||
$changed = true;
|
||||
$logAction = "Remove Party";
|
||||
$logDetails = "Removed political party: $partyToRemove";
|
||||
}
|
||||
|
||||
if ($changed) {
|
||||
$stmt = $db->prepare("UPDATE Election_History SET Parties = ?, Positions = ? WHERE Election_ID = ?");
|
||||
$stmt->execute([json_encode($electionParties), json_encode($electionPositions), $electionId]);
|
||||
|
||||
// Log to Audit Trail
|
||||
$Auth->Log_Action($_SESSION['User_ID'], $_SESSION['User_Role'], $logAction, $logDetails);
|
||||
|
||||
header("Location: Candidates.php");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all available tracks from Voters table
|
||||
$availableTracks = $db->query("SELECT DISTINCT Track_Cluster FROM Voters WHERE Track_Cluster IS NOT NULL AND Track_Cluster != '' ORDER BY Track_Cluster")->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
// Filters (for Ongoing/Finished)
|
||||
$search = $_GET['search'] ?? '';
|
||||
$filterPosition = $_GET['position'] ?? '';
|
||||
$filterParty = $_GET['party'] ?? '';
|
||||
$filterGrade = $_GET['grade'] ?? '';
|
||||
|
||||
// Fetch Stats
|
||||
$totalCandidates = $db->query("SELECT COUNT(*) FROM Candidates")->fetchColumn();
|
||||
$positionsCount = count($electionPositions) ?: $db->query("SELECT COUNT(DISTINCT Position) FROM Candidates")->fetchColumn();
|
||||
$partiesCount = count($electionParties) ?: $db->query("SELECT COUNT(DISTINCT Party) FROM Candidates")->fetchColumn();
|
||||
|
||||
// Fetch Breakdowns
|
||||
$positionsBreakdown = $db->query("SELECT Position as label, COUNT(*) as value FROM Candidates GROUP BY Position ORDER BY value DESC")->fetchAll();
|
||||
$partiesBreakdown = $db->query("SELECT Party as label, COUNT(*) as value FROM Candidates GROUP BY Party ORDER BY value DESC")->fetchAll();
|
||||
$grades = $db->query("SELECT Grade_Level as label, COUNT(*) as value FROM Candidates GROUP BY Grade_Level ORDER BY Grade_Level ASC")->fetchAll();
|
||||
|
||||
// Build Query for List
|
||||
$queryStr = "SELECT * FROM Candidates WHERE 1=1";
|
||||
$params = [];
|
||||
|
||||
if ($search) {
|
||||
$queryStr .= " AND (Name LIKE ? OR Email LIKE ? OR Party LIKE ?)";
|
||||
$params[] = "%$search%";
|
||||
$params[] = "%$search%";
|
||||
$params[] = "%$search%";
|
||||
}
|
||||
if ($filterPosition) {
|
||||
$queryStr .= " AND Position = ?";
|
||||
$params[] = $filterPosition;
|
||||
}
|
||||
if ($filterParty) {
|
||||
$queryStr .= " AND Party = ?";
|
||||
$params[] = $filterParty;
|
||||
}
|
||||
if ($filterGrade) {
|
||||
$queryStr .= " AND Grade_Level = ?";
|
||||
$params[] = $filterGrade;
|
||||
}
|
||||
|
||||
$queryStr .= " ORDER BY Position ASC, Name ASC";
|
||||
$stmt = $db->prepare($queryStr);
|
||||
$stmt->execute($params);
|
||||
$candidatesList = $stmt->fetchAll();
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Candidate Management | Online School Election System</title>
|
||||
<link rel="stylesheet" href="../Design/Style.css?v=<?php echo time(); ?>">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
.Candidate_Actions_Btn {
|
||||
height: 44px;
|
||||
padding: 0 24px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 0;
|
||||
width: auto;
|
||||
}
|
||||
.Candidate_Photo_Circle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: var(--Primary_Light);
|
||||
color: var(--Primary_Color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center; font-weight: 700;
|
||||
overflow: hidden;
|
||||
object-fit: cover;
|
||||
}
|
||||
.Editor_Grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.Editor_Card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
border: 1px solid var(--Border_Color);
|
||||
}
|
||||
.Item_Tag {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
background: var(--Primary_Light);
|
||||
color: var(--Primary_Color);
|
||||
padding: 8px 14px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
margin: 0 8px 8px 0;
|
||||
position: relative;
|
||||
min-width: 120px;
|
||||
}
|
||||
.Item_Tag_Header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.Item_Tag_Type {
|
||||
font-size: 0.65rem;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.7;
|
||||
font-weight: 700;
|
||||
}
|
||||
.Item_Tag i {
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.Item_Tag i:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.Status_Indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 16px;
|
||||
border-radius: 30px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.Status_Preparing { background: #FEF3C7; color: #92400E; }
|
||||
.Status_Ongoing { background: #DCFCE7; color: #166534; }
|
||||
.Status_Finished { background: #F1F5F9; color: #475569; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="Dashboard_Container">
|
||||
<!-- Sidebar -->
|
||||
<aside class="Sidebar">
|
||||
<div class="Sidebar_Header">
|
||||
<div class="Logo_Text">
|
||||
<div class="Logo_Title" style="font-size: 1.1rem; color: var(--Primary_Color);">Click to Vote</div>
|
||||
<div class="Logo_Subtitle">Administrator Portal</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="Sidebar_Nav">
|
||||
<a href="Election_Dashboard.php" class="Nav_Item">
|
||||
<i class="fas fa-th-large"></i> Election Dashboard
|
||||
</a>
|
||||
<a href="Election_History.php" class="Nav_Item">
|
||||
<i class="fas fa-vote-yea"></i> Election History
|
||||
</a>
|
||||
<a href="Voters.php" class="Nav_Item">
|
||||
<i class="fas fa-users"></i> Voter Management
|
||||
</a>
|
||||
<a href="Candidates.php" class="Nav_Item Active">
|
||||
<i class="fas fa-user-tie"></i> Candidate Management
|
||||
</a>
|
||||
<a href="Officers.php" class="Nav_Item">
|
||||
<i class="fas fa-user-shield"></i> Officers Management
|
||||
</a>
|
||||
<a href="Audit_Trail.php" class="Nav_Item">
|
||||
<i class="fas fa-file-alt"></i> Reports & Audit
|
||||
</a>
|
||||
<a href="Credits.php" class="Nav_Item">
|
||||
<i class="fas fa-info-circle"></i> Credits
|
||||
</a>
|
||||
</nav>
|
||||
<div style="padding: 24px; border-top: 1px solid var(--Border_Color);">
|
||||
<a href="Logout.php" class="Nav_Item" style="padding: 0; color: var(--Error_Color);">
|
||||
<i class="fas fa-sign-out-alt"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="Main_Content">
|
||||
<!-- Top Bar -->
|
||||
<header class="Top_Bar">
|
||||
<div class="Search_Box">
|
||||
<i class="fas fa-search" style="color: var(--Secondary_Color);"></i>
|
||||
<input type="text" placeholder="Quick search...">
|
||||
</div>
|
||||
<div class="Top_Bar_Actions">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div style="text-align: right;">
|
||||
<div style="font-weight: 700; font-size: 0.9rem;"><?php echo htmlspecialchars($_SESSION['User_Name']); ?></div>
|
||||
<div style="font-size: 0.75rem; color: var(--Text_Secondary);"><?php echo htmlspecialchars($_SESSION['User_Role']); ?></div>
|
||||
</div>
|
||||
<div style="width: 40px; height: 40px; background: var(--Primary_Light); color: var(--Primary_Color); border-radius: 50%; display: flex; justify-content: center; align-items: center; font-weight: 700;">
|
||||
<?php echo strtoupper(substr($_SESSION['User_Name'] ?? 'A', 0, 1)); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Content Body -->
|
||||
<div class="Content_Body">
|
||||
<div class="Page_Title_Section">
|
||||
<div style="display: flex; align-items: center; gap: 15px;">
|
||||
<div style="width: 48px; height: 48px; background: var(--Primary_Light); color: var(--Primary_Color); border-radius: 12px; display: flex; justify-content: center; align-items: center; font-size: 1.5rem;">
|
||||
<i class="fas fa-user-tie"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="Page_Title">Candidate Management</h1>
|
||||
<p class="Page_Subtitle">Managing SY <?php echo $election['Year'] ?? date('Y'); ?> Election</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="Status_Indicator Status_<?php echo $electionStatus; ?>">
|
||||
<i class="fas fa-circle" style="font-size: 0.6rem;"></i>
|
||||
<?php echo $electionStatus; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($electionStatus === 'Preparing'): ?>
|
||||
<!-- EDITORS (Preparing Mode) -->
|
||||
<div class="Editor_Grid">
|
||||
<!-- Positions Editor -->
|
||||
<div class="Editor_Card">
|
||||
<h3 class="Card_Title" style="margin-bottom: 15px;"><i class="fas fa-id-badge" style="color: var(--Primary_Color); margin-right: 10px;"></i> Define Positions</h3>
|
||||
<div style="min-height: 80px; margin-bottom: 20px;">
|
||||
<?php foreach ($electionPositions as $pos): ?>
|
||||
<span class="Item_Tag">
|
||||
<div class="Item_Tag_Header">
|
||||
<span><?php echo htmlspecialchars($pos['name']); ?></span>
|
||||
<form method="POST" style="display: inline;">
|
||||
<input type="hidden" name="action" value="remove_position">
|
||||
<input type="hidden" name="position" value="<?php echo htmlspecialchars($pos['name']); ?>">
|
||||
<i class="fas fa-times" onclick="this.parentElement.submit()"></i>
|
||||
</form>
|
||||
</div>
|
||||
<div class="Item_Tag_Type"><?php echo htmlspecialchars($pos['type'] ?? 'Uniform'); ?></div>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<form method="POST" style="display: flex; flex-direction: column; gap: 10px;">
|
||||
<input type="hidden" name="action" value="add_position">
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<input type="text" name="new_position" class="Input" placeholder="Position name..." required style="flex: 2;">
|
||||
<select name="position_type" class="Select" required style="flex: 1; height: 48px;">
|
||||
<option value="Uniform">Uniform</option>
|
||||
<option value="Track/Strand Specific">Track/Strand Specific</option>
|
||||
</select>
|
||||
<button type="submit" class="Button_Primary" style="width: auto; margin-top: 0; padding: 0 20px;"><i class="fas fa-plus"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Parties Editor -->
|
||||
<div class="Editor_Card">
|
||||
<h3 class="Card_Title" style="margin-bottom: 15px;"><i class="fas fa-flag" style="color: var(--Success_Color); margin-right: 10px;"></i> Define Parties</h3>
|
||||
<div style="min-height: 80px; margin-bottom: 20px;">
|
||||
<?php foreach ($electionParties as $party): ?>
|
||||
<span class="Item_Tag" style="background: #DCFCE7; color: #166534; flex-direction: row; align-items: center; justify-content: space-between; min-width: auto; padding: 6px 14px; border-radius: 20px;">
|
||||
<?php echo htmlspecialchars($party); ?>
|
||||
<form method="POST" style="display: inline; margin-left: 8px;">
|
||||
<input type="hidden" name="action" value="remove_party">
|
||||
<input type="hidden" name="party" value="<?php echo htmlspecialchars($party); ?>">
|
||||
<i class="fas fa-times" onclick="this.parentElement.submit()"></i>
|
||||
</form>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<form method="POST" style="display: flex; gap: 10px;">
|
||||
<input type="hidden" name="action" value="add_party">
|
||||
<input type="text" name="new_party" class="Input" placeholder="Enter party name..." required>
|
||||
<button type="submit" class="Button_Primary" style="width: auto; margin-top: 0; padding: 0 20px; background: var(--Success_Color);"><i class="fas fa-plus"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Candidate Form (Bottom) -->
|
||||
<div class="Card">
|
||||
<div class="Card_Header">
|
||||
<h2 class="Card_Title">Register New Candidate</h2>
|
||||
</div>
|
||||
<form method="POST" action="../Logic/Backend/Add_Candidate_Handler.php" enctype="multipart/form-data" style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px;">
|
||||
<div class="Form_Group">
|
||||
<label class="Label">Full Name</label>
|
||||
<input type="text" name="name" class="Input" placeholder="e.g. Juan Dela Cruz" required>
|
||||
</div>
|
||||
<div class="Form_Group">
|
||||
<label class="Label">Email Address</label>
|
||||
<input type="email" name="email" class="Input" placeholder="juan@school.edu" required>
|
||||
</div>
|
||||
<div class="Form_Group">
|
||||
<label class="Label">User ID</label>
|
||||
<input type="text" name="user_id" class="Input" placeholder="Leave blank for random">
|
||||
</div>
|
||||
<div class="Form_Group">
|
||||
<label class="Label">Position</label>
|
||||
<select name="position" class="Select" style="width: 100%; height: 50px;" required>
|
||||
<option value="">Select Position</option>
|
||||
<?php foreach ($electionPositions as $p): ?>
|
||||
<option value="<?php echo htmlspecialchars($p['name']); ?>"><?php echo htmlspecialchars($p['name']); ?> (<?php echo $p['type']; ?>)</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="Form_Group">
|
||||
<label class="Label">Political Party</label>
|
||||
<select name="party" class="Select" style="width: 100%; height: 50px;" required>
|
||||
<option value="">Select Party</option>
|
||||
<option value="Independent">Independent</option>
|
||||
<?php foreach ($electionParties as $p): ?>
|
||||
<option value="<?php echo htmlspecialchars($p); ?>"><?php echo htmlspecialchars($p); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="Form_Group">
|
||||
<label class="Label">Grade Level</label>
|
||||
<select name="grade" class="Select" style="width: 100%; height: 50px;" required>
|
||||
<option value="11">Grade 11</option>
|
||||
<option value="12">Grade 12</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="Form_Group">
|
||||
<label class="Label">Track / Strand</label>
|
||||
<select name="track" class="Select" style="width: 100%; height: 50px;" required>
|
||||
<option value="">Select Track/Strand</option>
|
||||
<?php foreach ($availableTracks as $t): ?>
|
||||
<option value="<?php echo htmlspecialchars($t); ?>"><?php echo htmlspecialchars($t); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="Form_Group">
|
||||
<label class="Label">Password</label>
|
||||
<input type="password" name="password" class="Input" placeholder="Leave blank for default">
|
||||
</div>
|
||||
<div class="Form_Group">
|
||||
<label class="Label">Candidate Photo</label>
|
||||
<input type="file" name="photo" class="Input" accept="image/*">
|
||||
</div>
|
||||
<div class="Form_Group" style="grid-column: span 3; display: flex; justify-content: flex-end;">
|
||||
<button type="submit" class="Button_Primary" style="margin-top: 0; width: 200px;"><i class="fas fa-user-plus"></i> Add Candidate</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<!-- DASHBOARD VIEW (Ongoing/Finished Mode) -->
|
||||
|
||||
<!-- Primary Stats -->
|
||||
<div class="Stat_Cards_Grid" style="margin-bottom: 20px;">
|
||||
<div class="Stat_Card">
|
||||
<div class="Stat_Label" style="text-transform: uppercase; font-size: 0.7rem; font-weight: 700;">Total Candidates</div>
|
||||
<div class="Stat_Value" style="color: var(--Primary_Color); font-size: 2.5rem; margin-top: 10px;"><?php echo $totalCandidates; ?></div>
|
||||
</div>
|
||||
<div class="Stat_Card">
|
||||
<div class="Stat_Label" style="text-transform: uppercase; font-size: 0.7rem; font-weight: 700; color: var(--Secondary_Color);">Unique Positions</div>
|
||||
<div class="Stat_Value" style="color: var(--Secondary_Color); font-size: 2.5rem; margin-top: 10px;"><?php echo $positionsCount; ?></div>
|
||||
</div>
|
||||
<div class="Stat_Card">
|
||||
<div class="Stat_Label" style="text-transform: uppercase; font-size: 0.7rem; font-weight: 700; color: var(--Success_Color);">Active Parties</div>
|
||||
<div class="Stat_Value" style="color: var(--Success_Color); font-size: 2.5rem; margin-top: 10px;"><?php echo $partiesCount; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Breakdown Stats -->
|
||||
<div class="Summary_Breakdowns">
|
||||
<div class="Breakdown_Card">
|
||||
<h3 class="Breakdown_Title">Candidates by Position</h3>
|
||||
<?php if (empty($positionsBreakdown)): ?><p style="font-size: 0.8rem; color: var(--Text_Secondary);">No data available</p><?php endif; ?>
|
||||
<?php foreach (array_slice($positionsBreakdown, 0, 5) as $pos): ?>
|
||||
<div class="Breakdown_Item">
|
||||
<span><?php echo htmlspecialchars($pos['label'] ?: 'N/A'); ?></span>
|
||||
<span style="font-weight: 700;"><?php echo $pos['value']; ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="Breakdown_Card">
|
||||
<h3 class="Breakdown_Title">Candidates by Party</h3>
|
||||
<?php if (empty($partiesBreakdown)): ?><p style="font-size: 0.8rem; color: var(--Text_Secondary);">No data available</p><?php endif; ?>
|
||||
<?php foreach (array_slice($partiesBreakdown, 0, 5) as $party): ?>
|
||||
<div class="Breakdown_Item">
|
||||
<span><?php echo htmlspecialchars($party['label'] ?: 'Independent'); ?></span>
|
||||
<span style="font-weight: 700;"><?php echo $party['value']; ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="Breakdown_Card">
|
||||
<h3 class="Breakdown_Title">Candidates by Grade</h3>
|
||||
<?php if (empty($grades)): ?><p style="font-size: 0.8rem; color: var(--Text_Secondary);">No data available</p><?php endif; ?>
|
||||
<?php foreach ($grades as $grade): ?>
|
||||
<div class="Breakdown_Item">
|
||||
<span>Grade <?php echo htmlspecialchars($grade['label'] ?: 'N/A'); ?></span>
|
||||
<span style="font-weight: 700;"><?php echo $grade['value']; ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table Card -->
|
||||
<div class="Card" style="padding: 0; overflow: hidden;">
|
||||
<!-- Filters -->
|
||||
<form method="GET" class="Filter_Bar" style="display: flex; flex-wrap: nowrap; gap: 20px; align-items: flex-end; background: transparent; border: none; border-bottom: 1px solid var(--Border_Color); border-radius: 0; margin-bottom: 0; padding: 24px;">
|
||||
<div class="Filter_Group" style="flex: 2;">
|
||||
<label>Search</label>
|
||||
<div class="Search_Input_Wrapper">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" name="search" class="Input" placeholder="Search by name, email or party" value="<?php echo htmlspecialchars($search); ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="Filter_Group" style="flex: 1;">
|
||||
<label>Position</label>
|
||||
<select name="position" class="Select" onchange="this.form.submit()" style="width: 100%;">
|
||||
<option value="">All Positions</option>
|
||||
<?php foreach ($electionPositions as $p): ?>
|
||||
<option value="<?php echo htmlspecialchars($p['name']); ?>" <?php echo $filterPosition == $p['name'] ? 'selected' : ''; ?>><?php echo htmlspecialchars($p['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="Filter_Group" style="flex: 1;">
|
||||
<label>Party</label>
|
||||
<select name="party" class="Select" onchange="this.form.submit()" style="width: 100%;">
|
||||
<option value="">All Parties</option>
|
||||
<?php foreach ($electionParties as $p): ?>
|
||||
<option value="<?php echo htmlspecialchars($p); ?>" <?php echo $filterParty == $p ? 'selected' : ''; ?>><?php echo htmlspecialchars($p); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="Filter_Group" style="flex: 1;">
|
||||
<label>Grade</label>
|
||||
<select name="grade" class="Select" onchange="this.form.submit()" style="width: 100%;">
|
||||
<option value="">All Grades</option>
|
||||
<?php
|
||||
$allGradesList = $db->query("SELECT DISTINCT Grade_Level FROM Candidates WHERE Grade_Level IS NOT NULL ORDER BY Grade_Level")->fetchAll(PDO::FETCH_COLUMN);
|
||||
foreach ($allGradesList as $g): ?>
|
||||
<option value="<?php echo htmlspecialchars($g); ?>" <?php echo $filterGrade == $g ? 'selected' : ''; ?>>Grade <?php echo htmlspecialchars($g); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="Table_Wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Candidate</th>
|
||||
<th>Position</th>
|
||||
<th>Party</th>
|
||||
<th>Grade/Track</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($candidatesList)): ?>
|
||||
<tr>
|
||||
<td colspan="5" style="text-align: center; padding: 40px; color: var(--Text_Secondary);">
|
||||
No candidates found matching your criteria.
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($candidatesList as $candidate): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div class="Candidate_Photo_Circle">
|
||||
<?php if (!empty($candidate['Photo'])): ?>
|
||||
<img src="<?php echo htmlspecialchars($candidate['Photo']); ?>" alt="" style="width: 100%; height: 100%; object-fit: cover;">
|
||||
<?php else: ?>
|
||||
<?php echo strtoupper(substr($candidate['Name'] ?? 'C', 0, 1)); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-weight: 600;"><?php echo htmlspecialchars($candidate['Name']); ?></div>
|
||||
<div style="font-size: 0.75rem; color: var(--Text_Secondary);"><?php echo htmlspecialchars($candidate['User_ID']); ?> | <?php echo htmlspecialchars($candidate['Email']); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="Badge" style="background: var(--Primary_Light); color: var(--Primary_Color);"><?php echo htmlspecialchars($candidate['Position'] ?: 'N/A'); ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<div style="font-weight: 500;"><?php echo htmlspecialchars($candidate['Party'] ?: 'Independent'); ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<div style="font-size: 0.9rem;">Grade <?php echo htmlspecialchars($candidate['Grade_Level'] ?: 'N/A'); ?></div>
|
||||
<div style="font-size: 0.75rem; color: var(--Text_Secondary);"><?php echo htmlspecialchars($candidate['Track_Cluster'] ?: 'N/A'); ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button title="Edit" style="background: none; border: none; color: var(--Secondary_Color); cursor: pointer; font-size: 1rem;"><i class="fas fa-edit"></i></button>
|
||||
<button title="Delete" style="background: none; border: none; color: var(--Error_Color); cursor: pointer; font-size: 1rem;"><i class="fas fa-trash"></i></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="padding: 15px 24px; font-size: 0.85rem; color: var(--Text_Secondary); border-top: 1px solid var(--Border_Color);">
|
||||
Showing <?php echo count($candidatesList); ?> of <?php echo $totalCandidates; ?> candidates
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
105
Screens/Credits.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../Logic/Backend/Authentication_Handler.php';
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
$Auth = new Authentication_Handler();
|
||||
$Auth->Check_Auth();
|
||||
|
||||
if ($_SESSION['Access_Level'] < 1) {
|
||||
header('Location: Voting_Screen.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Credits | Online School Election System</title>
|
||||
<link rel="stylesheet" href="../Design/Style.css?v=<?php echo time(); ?>">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="Dashboard_Container">
|
||||
<!-- Sidebar -->
|
||||
<aside class="Sidebar">
|
||||
<div class="Sidebar_Header">
|
||||
<div class="Logo_Text">
|
||||
<div class="Logo_Title" style="font-size: 1.1rem; color: var(--Primary_Color);">Click to Vote</div>
|
||||
<div class="Logo_Subtitle">Administrator Portal</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="Sidebar_Nav">
|
||||
<a href="Election_Dashboard.php" class="Nav_Item">
|
||||
<i class="fas fa-th-large"></i> Election Dashboard
|
||||
</a>
|
||||
<a href="Election_History.php" class="Nav_Item">
|
||||
<i class="fas fa-vote-yea"></i> Election History
|
||||
</a>
|
||||
<a href="Voters.php" class="Nav_Item">
|
||||
<i class="fas fa-users"></i> Voter Management
|
||||
</a>
|
||||
<a href="Candidates.php" class="Nav_Item">
|
||||
<i class="fas fa-user-tie"></i> Candidate Management
|
||||
</a>
|
||||
<a href="Officers.php" class="Nav_Item">
|
||||
<i class="fas fa-user-shield"></i> Officers Management
|
||||
</a>
|
||||
<a href="Audit_Trail.php" class="Nav_Item">
|
||||
<i class="fas fa-file-alt"></i> Reports & Audit
|
||||
</a>
|
||||
<a href="Credits.php" class="Nav_Item Active">
|
||||
<i class="fas fa-info-circle"></i> Credits
|
||||
</a>
|
||||
</nav>
|
||||
<div style="padding: 24px; border-top: 1px solid var(--Border_Color);">
|
||||
<a href="Logout.php" class="Nav_Item" style="padding: 0; color: var(--Error_Color);">
|
||||
<i class="fas fa-sign-out-alt"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="Main_Content">
|
||||
<!-- Top Bar -->
|
||||
<header class="Top_Bar">
|
||||
<div class="Top_Bar_Actions" style="margin-left: auto;">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div style="text-align: right;">
|
||||
<div style="font-weight: 700; font-size: 0.9rem;"><?php echo htmlspecialchars($_SESSION['User_Name']); ?></div>
|
||||
<div style="font-size: 0.75rem; color: var(--Text_Secondary);"><?php echo htmlspecialchars($_SESSION['User_Role']); ?></div>
|
||||
</div>
|
||||
<div style="width: 40px; height: 40px; background: var(--Primary_Light); color: var(--Primary_Color); border-radius: 50%; display: flex; justify-content: center; align-items: center; font-weight: 700;">
|
||||
<?php echo strtoupper(substr($_SESSION['User_Name'] ?? 'A', 0, 1)); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Content Body -->
|
||||
<div class="Content_Body">
|
||||
<div class="Page_Title_Section">
|
||||
<h1 class="Page_Title">Credits</h1>
|
||||
<p class="Page_Subtitle">Information about the application development</p>
|
||||
</div>
|
||||
|
||||
<div class="Card" style="text-align: center; padding: 60px;">
|
||||
<div style="margin-bottom: 30px;">
|
||||
<img src="https://flatlogic.com/assets/logo-flatlogic-d698f1f54497525330e6211116631f24.svg" alt="Flatlogic Logo" style="width: 200px; margin-bottom: 20px;">
|
||||
<h2 style="font-size: 2rem; color: var(--Primary_Color); margin-bottom: 10px;">Built with Flatlogic</h2>
|
||||
<p class="Text_Muted" style="max-width: 600px; margin: 0 auto; line-height: 1.8;">
|
||||
This Online School Election System was built using the Flatlogic Platform.
|
||||
Flatlogic is a platform for building full-stack web applications in minutes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: center; gap: 20px; margin-top: 40px;">
|
||||
<a href="https://flatlogic.com" target="_blank" class="Button_Primary" style="width: auto; padding: 12px 30px; text-decoration: none;">Visit Flatlogic</a>
|
||||
<a href="https://flatlogic.com/documentation" target="_blank" class="Button_Secondary" style="width: auto; padding: 12px 30px; text-decoration: none;">Documentation</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
338
Screens/Election_Dashboard.php
Normal file
@ -0,0 +1,338 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../Logic/Backend/Authentication_Handler.php';
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
$Auth = new Authentication_Handler();
|
||||
$Auth->Check_Auth();
|
||||
|
||||
if ($_SESSION['Access_Level'] < 1) {
|
||||
header('Location: Voting_Screen.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = db();
|
||||
|
||||
// Handle New Election Creation
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_election'])) {
|
||||
$year = $_POST['election_year'] ?? date('Y');
|
||||
|
||||
// Default configuration for a new election
|
||||
$defaultParties = json_encode(['Independent']);
|
||||
$defaultPositions = json_encode([
|
||||
['name' => 'President', 'type' => 'Uniform'],
|
||||
['name' => 'Vice President', 'type' => 'Uniform'],
|
||||
['name' => 'Secretary', 'type' => 'Uniform'],
|
||||
['name' => 'Treasurer', 'type' => 'Uniform']
|
||||
]);
|
||||
|
||||
$startDate = date('Y-m-d H:i:s');
|
||||
$endDate = date('Y-m-d H:i:s', strtotime('+7 days'));
|
||||
|
||||
try {
|
||||
$stmt = $db->prepare("INSERT INTO Election_History (Year, Parties, Positions, Status, Start_Date, End_Date, Total_Voters) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$year, $defaultParties, $defaultPositions, 'Preparing', $startDate, $endDate, 0]);
|
||||
|
||||
$Auth->Log_Action($_SESSION['User_ID'], $_SESSION['User_Role'], 'Create New Election', "Created a new election for School Year $year");
|
||||
|
||||
header("Location: Election_Dashboard.php?success=New election created successfully");
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
header("Location: Election_Dashboard.php?error=Error creating election: " . $e->getMessage());
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Status Change
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_status'])) {
|
||||
$newStatus = $_POST['new_status'];
|
||||
$electionId = $_POST['election_id'];
|
||||
|
||||
// Fetch current status
|
||||
$currentElectionStmt = $db->prepare("SELECT * FROM Election_History WHERE Election_ID = ?");
|
||||
$currentElectionStmt->execute([$electionId]);
|
||||
$electionData = $currentElectionStmt->fetch();
|
||||
|
||||
if ($newStatus === 'Finished') {
|
||||
// Save current details to history record
|
||||
$totalVoters = $db->query("SELECT COUNT(*) FROM Voters")->fetchColumn();
|
||||
$votedCount = $db->query("SELECT COUNT(*) FROM Voters WHERE Has_Voted = 1")->fetchColumn();
|
||||
|
||||
// Fetch Candidates and potentially calculate results if there was a voting table
|
||||
$candidates = $db->query("SELECT Name, Position, Party FROM Candidates")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$stmt = $db->prepare("UPDATE Election_History SET Status = ?, Total_Voters = ?, Results = ? WHERE Election_ID = ?");
|
||||
$stmt->execute([$newStatus, $totalVoters, json_encode($candidates), $electionId]);
|
||||
} else {
|
||||
$stmt = $db->prepare("UPDATE Election_History SET Status = ? WHERE Election_ID = ?");
|
||||
$stmt->execute([$newStatus, $electionId]);
|
||||
}
|
||||
|
||||
// Log to Audit Trail
|
||||
$year = $electionData['Year'] ?? 'Unknown';
|
||||
$oldStatus = $electionData['Status'] ?? 'Unknown';
|
||||
$Auth->Log_Action($_SESSION['User_ID'], $_SESSION['User_Role'], 'Update Election Status', "Changed SY $year Election status from $oldStatus to $newStatus");
|
||||
|
||||
header("Location: Election_Dashboard.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Fetch Stats
|
||||
$totalVoters = $db->query("SELECT COUNT(*) FROM Voters")->fetchColumn();
|
||||
$totalCandidates = $db->query("SELECT COUNT(*) FROM Candidates")->fetchColumn();
|
||||
$totalVotes = $db->query("SELECT COUNT(*) FROM Voters WHERE Has_Voted = 1")->fetchColumn();
|
||||
|
||||
// Fetch Active/Preparing/Ongoing Elections
|
||||
$activeElections = $db->query("SELECT * FROM Election_History WHERE Status IN ('Preparing', 'Ongoing', 'Active') ORDER BY Start_Date DESC")->fetchAll();
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Election Dashboard | Online School Election System</title>
|
||||
<link rel="stylesheet" href="../Design/Style.css?v=<?php echo time(); ?>">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
.Status_Badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.Status_Active, .Status_Ongoing { background: #DCFCE7; color: #166534; }
|
||||
.Status_Preparing { background: #FEF3C7; color: #92400E; }
|
||||
.Status_Completed, .Status_Finished { background: #F1F5F9; color: #475569; }
|
||||
|
||||
/* Modal Styles */
|
||||
.Modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 1000;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.Modal_Content {
|
||||
background: white;
|
||||
padding: 32px;
|
||||
border-radius: 20px;
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
position: relative;
|
||||
box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1);
|
||||
}
|
||||
.Modal_Header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.Modal_Title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.Modal_Close {
|
||||
cursor: pointer;
|
||||
font-size: 1.25rem;
|
||||
color: var(--Text_Secondary);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="Dashboard_Container">
|
||||
<!-- Sidebar -->
|
||||
<aside class="Sidebar">
|
||||
<div class="Sidebar_Header">
|
||||
<div class="Logo_Text">
|
||||
<div class="Logo_Title" style="font-size: 1.1rem; color: var(--Primary_Color);">Click to Vote</div>
|
||||
<div class="Logo_Subtitle">Administrator Portal</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="Sidebar_Nav">
|
||||
<a href="Election_Dashboard.php" class="Nav_Item Active">
|
||||
<i class="fas fa-th-large"></i> Election Dashboard
|
||||
</a>
|
||||
<a href="Election_History.php" class="Nav_Item">
|
||||
<i class="fas fa-vote-yea"></i> Election History
|
||||
</a>
|
||||
<a href="Voters.php" class="Nav_Item">
|
||||
<i class="fas fa-users"></i> Voter Management
|
||||
</a>
|
||||
<a href="Candidates.php" class="Nav_Item">
|
||||
<i class="fas fa-user-tie"></i> Candidate Management
|
||||
</a>
|
||||
<a href="Officers.php" class="Nav_Item">
|
||||
<i class="fas fa-user-shield"></i> Officers Management
|
||||
</a>
|
||||
<a href="Audit_Trail.php" class="Nav_Item">
|
||||
<i class="fas fa-file-alt"></i> Reports & Audit
|
||||
</a>
|
||||
<a href="Credits.php" class="Nav_Item">
|
||||
<i class="fas fa-info-circle"></i> Credits
|
||||
</a>
|
||||
</nav>
|
||||
<div style="padding: 24px; border-top: 1px solid var(--Border_Color);">
|
||||
<a href="Logout.php" class="Nav_Item" style="padding: 0; color: var(--Error_Color);">
|
||||
<i class="fas fa-sign-out-alt"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="Main_Content">
|
||||
<!-- Top Bar -->
|
||||
<header class="Top_Bar">
|
||||
<div class="Search_Box">
|
||||
<i class="fas fa-search" style="color: var(--Secondary_Color);"></i>
|
||||
<input type="text" placeholder="Search for voters, candidates, or records...">
|
||||
</div>
|
||||
<div class="Top_Bar_Actions">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div style="text-align: right;">
|
||||
<div style="font-weight: 700; font-size: 0.9rem;"><?php echo htmlspecialchars($_SESSION['User_Name']); ?></div>
|
||||
<div style="font-size: 0.75rem; color: var(--Text_Secondary);"><?php echo htmlspecialchars($_SESSION['User_Role']); ?></div>
|
||||
</div>
|
||||
<div style="width: 40px; height: 40px; background: var(--Primary_Light); color: var(--Primary_Color); border-radius: 50%; display: flex; justify-content: center; align-items: center; font-weight: 700;">
|
||||
<?php echo strtoupper(substr($_SESSION['User_Name'] ?? 'A', 0, 1)); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Content Body -->
|
||||
<div class="Content_Body">
|
||||
<?php if (isset($_GET['success'])): ?>
|
||||
<div class="Badge Badge_Success" style="width: 100%; padding: 15px; margin-bottom: 20px; text-align: center;">
|
||||
<i class="fas fa-check-circle"></i> <?php echo htmlspecialchars($_GET['success']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($_GET['error'])): ?>
|
||||
<div class="Badge" style="width: 100%; padding: 15px; margin-bottom: 20px; text-align: center; background: #FEE2E2; color: #EF4444;">
|
||||
<i class="fas fa-exclamation-circle"></i> <?php echo htmlspecialchars($_GET['error']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="Page_Title_Section">
|
||||
<h1 class="Page_Title">Election Dashboard</h1>
|
||||
<p class="Page_Subtitle">Welcome back! Here's what's happening with the current elections.</p>
|
||||
</div>
|
||||
|
||||
<!-- Stat Cards -->
|
||||
<div class="Stat_Cards_Grid">
|
||||
<div class="Stat_Card">
|
||||
<div class="Stat_Label">Total Voters</div>
|
||||
<div class="Stat_Value"><?php echo number_format($totalVoters); ?></div>
|
||||
<div class="Stat_Trend Trend_Up"><i class="fas fa-users"></i> Registered Students</div>
|
||||
</div>
|
||||
<div class="Stat_Card">
|
||||
<div class="Stat_Label">Total Candidates</div>
|
||||
<div class="Stat_Value"><?php echo number_format($totalCandidates); ?></div>
|
||||
<div class="Stat_Trend" style="color: var(--Primary_Color);"><i class="fas fa-user-tie"></i> Running for Office</div>
|
||||
</div>
|
||||
<div class="Stat_Card">
|
||||
<div class="Stat_Label">Total Votes Cast</div>
|
||||
<div class="Stat_Value"><?php echo number_format($totalVotes); ?></div>
|
||||
<div class="Stat_Trend Trend_Up"><i class="fas fa-check-circle"></i> Verified Ballots</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dashboard Grid -->
|
||||
<div class="Dashboard_Grid">
|
||||
<!-- Main Content: Active Elections -->
|
||||
<div class="Card">
|
||||
<div class="Card_Header">
|
||||
<h2 class="Card_Title">Active & Upcoming Elections</h2>
|
||||
<button onclick="openNewElectionModal()" class="Button_Primary" style="width: auto; padding: 8px 16px; font-size: 0.85rem;">
|
||||
<i class="fas fa-plus"></i> New Election
|
||||
</button>
|
||||
</div>
|
||||
<div class="Table_Wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Election Title</th>
|
||||
<th>Period</th>
|
||||
<th>Status</th>
|
||||
<th>Quick Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($activeElections)): ?>
|
||||
<tr>
|
||||
<td colspan="4" style="text-align: center; padding: 40px; color: var(--Text_Secondary);">
|
||||
No active elections found. <a href="javascript:void(0)" onclick="openNewElectionModal()" style="color: var(--Primary_Color); font-weight: 600;">Create one now.</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($activeElections as $election): ?>
|
||||
<tr>
|
||||
<td style="font-weight: 600;">School Year <?php echo $election['Year']; ?> Election</td>
|
||||
<td><?php echo date('M d', strtotime($election['Start_Date'])); ?> - <?php echo date('M d, Y', strtotime($election['End_Date'])); ?></td>
|
||||
<td>
|
||||
<span class="Status_Badge Status_<?php echo $election['Status']; ?>">
|
||||
<?php echo $election['Status']; ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<form method="POST" style="display: inline-flex; gap: 8px; align-items: center;">
|
||||
<input type="hidden" name="election_id" value="<?php echo $election['Election_ID']; ?>">
|
||||
<select name="new_status" class="Select" style="font-size: 0.75rem; padding: 4px 8px;">
|
||||
<option value="Preparing" <?php echo $election['Status'] == 'Preparing' ? 'selected' : ''; ?>>Preparing</option>
|
||||
<option value="Ongoing" <?php echo $election['Status'] == 'Ongoing' ? 'selected' : ''; ?>>Ongoing</option>
|
||||
<option value="Finished" <?php echo $election['Status'] == 'Finished' ? 'selected' : ''; ?>>Finished</option>
|
||||
</select>
|
||||
<button type="submit" name="update_status" class="Button_Primary" style="width: auto; padding: 6px 10px; margin-top: 0; font-size: 0.7rem;">Update</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- New Election Modal -->
|
||||
<div id="NewElectionModal" class="Modal">
|
||||
<div class="Modal_Content">
|
||||
<div class="Modal_Header">
|
||||
<h2 class="Modal_Title">Create New Election</h2>
|
||||
<span class="Modal_Close" onclick="closeNewElectionModal()">×</span>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<div class="Form_Group">
|
||||
<label class="Label">School Year</label>
|
||||
<input type="number" name="election_year" class="Input" value="<?php echo date('Y'); ?>" min="2000" max="2100" required>
|
||||
<p class="Text_Muted" style="font-size: 0.75rem; margin-top: 5px;">Example: 2026 for SY 2026-2027</p>
|
||||
</div>
|
||||
<button type="submit" name="create_election" class="Button_Primary" style="width: 100%; margin-top: 24px;">Initialize Election</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openNewElectionModal() {
|
||||
document.getElementById('NewElectionModal').style.display = 'flex';
|
||||
}
|
||||
function closeNewElectionModal() {
|
||||
document.getElementById('NewElectionModal').style.display = 'none';
|
||||
}
|
||||
window.onclick = function(event) {
|
||||
if (event.target == document.getElementById('NewElectionModal')) {
|
||||
closeNewElectionModal();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
207
Screens/Election_History.php
Normal file
@ -0,0 +1,207 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../Logic/Backend/Authentication_Handler.php';
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
$Auth = new Authentication_Handler();
|
||||
$Auth->Check_Auth();
|
||||
|
||||
if ($_SESSION['Access_Level'] < 1) {
|
||||
header('Location: Voting_Screen.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = db();
|
||||
|
||||
// Fetch all completed elections
|
||||
$history = $db->query("SELECT * FROM Election_History ORDER BY Year DESC")->fetchAll();
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Election History | Online School Election System</title>
|
||||
<link rel="stylesheet" href="../Design/Style.css?v=<?php echo time(); ?>">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
.Year_Selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="Dashboard_Container">
|
||||
<!-- Sidebar -->
|
||||
<aside class="Sidebar">
|
||||
<div class="Sidebar_Header">
|
||||
<div class="Logo_Text">
|
||||
<div class="Logo_Title" style="font-size: 1.1rem; color: var(--Primary_Color);">Click to Vote</div>
|
||||
<div class="Logo_Subtitle">Administrator Portal</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="Sidebar_Nav">
|
||||
<a href="Election_Dashboard.php" class="Nav_Item">
|
||||
<i class="fas fa-th-large"></i> Election Dashboard
|
||||
</a>
|
||||
<a href="Election_History.php" class="Nav_Item Active">
|
||||
<i class="fas fa-vote-yea"></i> Election History
|
||||
</a>
|
||||
<a href="Voters.php" class="Nav_Item">
|
||||
<i class="fas fa-users"></i> Voter Management
|
||||
</a>
|
||||
<a href="Candidates.php" class="Nav_Item">
|
||||
<i class="fas fa-user-tie"></i> Candidate Management
|
||||
</a>
|
||||
<a href="Officers.php" class="Nav_Item">
|
||||
<i class="fas fa-user-shield"></i> Officers Management
|
||||
</a>
|
||||
<a href="Audit_Trail.php" class="Nav_Item">
|
||||
<i class="fas fa-file-alt"></i> Reports & Audit
|
||||
</a>
|
||||
<a href="Credits.php" class="Nav_Item">
|
||||
<i class="fas fa-info-circle"></i> Credits
|
||||
</a>
|
||||
</nav>
|
||||
<div style="padding: 24px; border-top: 1px solid var(--Border_Color);">
|
||||
<a href="Logout.php" class="Nav_Item" style="padding: 0; color: var(--Error_Color);">
|
||||
<i class="fas fa-sign-out-alt"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="Main_Content">
|
||||
<!-- Top Bar -->
|
||||
<header class="Top_Bar">
|
||||
<div class="Search_Box">
|
||||
<i class="fas fa-search" style="color: var(--Secondary_Color);"></i>
|
||||
<input type="text" placeholder="Search for records...">
|
||||
</div>
|
||||
<div class="Top_Bar_Actions">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div style="text-align: right;">
|
||||
<div style="font-weight: 700; font-size: 0.9rem;"><?php echo htmlspecialchars($_SESSION['User_Name']); ?></div>
|
||||
<div style="font-size: 0.75rem; color: var(--Text_Secondary);"><?php echo htmlspecialchars($_SESSION['User_Role']); ?></div>
|
||||
</div>
|
||||
<div style="width: 40px; height: 40px; background: var(--Primary_Light); color: var(--Primary_Color); border-radius: 50%; display: flex; justify-content: center; align-items: center; font-weight: 700;">
|
||||
<?php echo strtoupper(substr($_SESSION['User_Name'] ?? 'A', 0, 1)); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Content Body -->
|
||||
<div class="Content_Body">
|
||||
<div class="Page_Title_Section">
|
||||
<div>
|
||||
<h1 class="Page_Title">Election History</h1>
|
||||
<p class="Page_Subtitle">Voter turnout and candidate results per election year</p>
|
||||
</div>
|
||||
<div class="Year_Selector">
|
||||
<select class="Select" id="JumpToYear">
|
||||
<option value="">Jump to School Year</option>
|
||||
<?php foreach ($history as $item): ?>
|
||||
<option value="year-<?php echo $item['Year']; ?>">School Year <?php echo $item['Year']; ?>–<?php echo $item['Year']+1; ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (empty($history)): ?>
|
||||
<div class="Card" style="text-align: center; padding: 60px;">
|
||||
<i class="fas fa-history" style="font-size: 3rem; color: var(--Border_Color); margin-bottom: 20px;"></i>
|
||||
<h3>No Election History Found</h3>
|
||||
<p class="Text_Muted">Completed elections will appear here once they are finalized.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($history as $index => $item): ?>
|
||||
<div class="Accordion_Item <?php echo $index === 0 ? 'Active' : ''; ?>" id="year-<?php echo $item['Year']; ?>">
|
||||
<div class="Accordion_Header">
|
||||
<h3>School Year <?php echo $item['Year']; ?>–<?php echo $item['Year']+1; ?></h3>
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<span class="Badge <?php echo $item['Status'] === 'Finished' ? 'Badge_Success' : 'Badge_Warning'; ?>"><?php echo $item['Status']; ?></span>
|
||||
<i class="fas fa-chevron-down Accordion_Icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Accordion_Content">
|
||||
<!-- Stats Overview -->
|
||||
<div class="Stat_Cards_Grid" style="margin-bottom: 24px;">
|
||||
<div class="Stat_Card">
|
||||
<div class="Stat_Label">Total Voters</div>
|
||||
<div class="Stat_Value"><?php echo $item['Total_Voters']; ?></div>
|
||||
</div>
|
||||
<div class="Stat_Card">
|
||||
<div class="Stat_Label">Election Period</div>
|
||||
<div style="font-size: 1rem; font-weight: 600;">
|
||||
<?php echo date('M d, Y', strtotime($item['Start_Date'])); ?> to <?php echo date('M d, Y', strtotime($item['End_Date'])); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Breakdown Tables -->
|
||||
<div class="Dashboard_Grid" style="grid-template-columns: 1fr; gap: 24px;">
|
||||
<div class="Card" style="padding: 0; box-shadow: none; border: 1px solid var(--Border_Color);">
|
||||
<div class="Card_Header" style="padding: 20px 24px; margin-bottom: 0; border-bottom: 1px solid var(--Border_Color);">
|
||||
<h4 class="Card_Title" style="font-size: 1rem;">Candidate Results</h4>
|
||||
</div>
|
||||
<div class="Table_Wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Candidate Name</th>
|
||||
<th>Position</th>
|
||||
<th>Party</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$results = json_decode($item['Results'] ?? '[]', true) ?: [];
|
||||
if (empty($results)):
|
||||
?>
|
||||
<tr><td colspan="3" style="text-align: center; padding: 20px;">No detailed results saved for this election.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($results as $candidate): ?>
|
||||
<tr>
|
||||
<td style="font-weight: 600;"><?php echo htmlspecialchars($candidate['Name']); ?></td>
|
||||
<td><?php echo htmlspecialchars($candidate['Position']); ?></td>
|
||||
<td><?php echo htmlspecialchars($candidate['Party']); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('.Accordion_Header').forEach(header => {
|
||||
header.addEventListener('click', () => {
|
||||
const item = header.parentElement;
|
||||
item.classList.toggle('Active');
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('JumpToYear').addEventListener('change', function() {
|
||||
const targetId = this.value;
|
||||
if (targetId) {
|
||||
const element = document.getElementById(targetId);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' });
|
||||
element.classList.add('Active');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
398
Screens/Login.php
Normal file
@ -0,0 +1,398 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (isset($_SESSION['User_ID'])) {
|
||||
header('Location: Election_Dashboard.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login | Online School Election System</title>
|
||||
<link rel="stylesheet" href="../Design/Style.css?v=<?php echo time(); ?>">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
:root {
|
||||
--Modal_Header_Bg: #4E66D1; /* Specific blue from screenshot */
|
||||
}
|
||||
|
||||
.Landing_Wrapper {
|
||||
background-image: url('../assets/pasted-20260204-200305-388a4105.jpg');
|
||||
}
|
||||
|
||||
.Landing_Info_Card {
|
||||
background: white;
|
||||
padding: 0;
|
||||
border-radius: 24px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
|
||||
max-width: 850px;
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.Landing_Card_Header {
|
||||
background: var(--Modal_Header_Bg);
|
||||
padding: 20px 30px;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.Landing_Card_Body {
|
||||
padding: 40px 48px;
|
||||
color: var(--Text_Primary);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.Landing_Title {
|
||||
font-size: clamp(1.5rem, 5vw, 2.5rem);
|
||||
margin-bottom: 16px;
|
||||
font-weight: 800;
|
||||
color: var(--Text_Primary);
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
}
|
||||
|
||||
.Landing_Description {
|
||||
margin-bottom: 32px;
|
||||
font-size: 1.1rem;
|
||||
color: var(--Text_Secondary);
|
||||
}
|
||||
|
||||
/* Modal Redesign */
|
||||
.Login_Card {
|
||||
padding: 0;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.Modal_Header {
|
||||
background: var(--Modal_Header_Bg);
|
||||
padding: 24px;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.Modal_Header_Content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.Modal_Header_Title {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.Modal_Header_Icon {
|
||||
font-size: 1.8rem;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
padding: 6px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.Btn_Close_Modal_New {
|
||||
background: rgba(15, 23, 42, 0.4);
|
||||
border: none;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.Btn_Close_Modal_New:hover {
|
||||
background: rgba(15, 23, 42, 0.6);
|
||||
}
|
||||
|
||||
.Modal_Body {
|
||||
padding: 32px 40px;
|
||||
}
|
||||
|
||||
.Form_Group_New {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.Label_New {
|
||||
display: block;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
color: #475569;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.Input_Wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.Input_Icon_Left {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #64748B;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.Input_New {
|
||||
width: 100%;
|
||||
padding: 14px 16px 14px 48px;
|
||||
border: 2px solid #E2E8F0;
|
||||
border-radius: 12px;
|
||||
font-size: 1rem;
|
||||
color: #1E293B;
|
||||
transition: all 0.2s;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.Input_New::placeholder {
|
||||
color: #94A3B8;
|
||||
}
|
||||
|
||||
.Input_New:focus {
|
||||
outline: none;
|
||||
border-color: var(--Modal_Header_Bg);
|
||||
box-shadow: 0 0 0 4px rgba(78, 102, 209, 0.1);
|
||||
}
|
||||
|
||||
.Select_New {
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2364748B'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 16px center;
|
||||
background-size: 18px;
|
||||
}
|
||||
|
||||
.Btn_Password_Toggle {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #64748B;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.Btn_Login_Submit {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
background: var(--Modal_Header_Bg);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
margin-top: 8px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.Btn_Login_Submit:hover {
|
||||
background: #3D54B8;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 8px 15px rgba(78, 102, 209, 0.3);
|
||||
}
|
||||
|
||||
.Forgot_Password {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
color: #4E66D1;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.Forgot_Password:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="Landing_Wrapper">
|
||||
<!-- Top Left Pill (Logo Section) -->
|
||||
<header class="Landing_Header">
|
||||
<div class="Logo_Pill">
|
||||
<div class="Logo_Circle">
|
||||
<img src="../assets/pasted-20260204-200612-dffb96f0.png" alt="School Logo">
|
||||
</div>
|
||||
<div class="Logo_Text">
|
||||
<div class="Logo_Title">Iloilo National High School</div>
|
||||
<div class="Logo_Subtitle">Luna St., La Paz, Iloilo City</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Landing Content wrapped in a card for visual separation -->
|
||||
<main class="Landing_Content">
|
||||
<div class="Landing_Info_Card">
|
||||
<div class="Landing_Card_Header">
|
||||
<i class="fas fa-info-circle" style="font-size: 1.5rem;"></i>
|
||||
<span style="font-weight: 700; letter-spacing: 1px;">SYSTEM INFORMATION</span>
|
||||
</div>
|
||||
<div class="Landing_Card_Body">
|
||||
<h1 class="Landing_Title">CLICK TO VOTE</h1>
|
||||
<p class="Landing_Description">
|
||||
This portal is currently a <strong>PROTOTYPE MODEL</strong> under active development to demonstrate the digital election process.
|
||||
Please be aware that all features are in a testing phase and interactions are for demonstration purposes only. We are
|
||||
continuously refining the system to ensure a seamless experience for the final implementation.
|
||||
</p>
|
||||
<button id="Open_Login_Btn" class="Btn_Landing_Login" style="background: var(--Modal_Header_Bg); color: white;">
|
||||
<i class="fas fa-sign-in-alt"></i> LOGIN
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="Landing_Footer">
|
||||
© 2026 Click to Vote System [PROTOTYPE]. All Rights Reserved.
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Login Modal -->
|
||||
<div id="Login_Modal" class="Modal_Overlay">
|
||||
<div class="Login_Card">
|
||||
<div class="Modal_Header">
|
||||
<div class="Modal_Header_Content">
|
||||
<i class="fas fa-users-viewfinder Modal_Header_Icon"></i>
|
||||
<h2 class="Modal_Header_Title">Login</h2>
|
||||
</div>
|
||||
<button class="Btn_Close_Modal_New" id="Close_Login_Btn" title="Close">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="Modal_Body">
|
||||
<div id="Error_Alert" class="Alert Alert_Error" style="padding: 12px; border-radius: 10px; font-size: 0.85rem; margin-bottom: 20px; display: none; background: #FEE2E2; color: #EF4444; border: 1px solid #FCA5A5;">
|
||||
<i class="fas fa-exclamation-circle"></i> <span id="Error_Message"></span>
|
||||
</div>
|
||||
|
||||
<form action="../Logic/Backend/Authentication_Handler.php" method="POST">
|
||||
<div class="Form_Group_New">
|
||||
<label class="Label_New" for="User_Type">User Type</label>
|
||||
<div class="Input_Wrapper">
|
||||
<i class="fas fa-user-check Input_Icon_Left"></i>
|
||||
<select class="Input_New Select_New" name="User_Type_Select" id="User_Type_Select">
|
||||
<option value="Voter">Voter</option>
|
||||
<option value="Adviser">Adviser</option>
|
||||
<option value="Officer">Officer</option>
|
||||
<option value="Admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Form_Group_New">
|
||||
<label class="Label_New" for="User_ID">UID</label>
|
||||
<div class="Input_Wrapper">
|
||||
<i class="fas fa-id-card Input_Icon_Left"></i>
|
||||
<input class="Input_New" type="text" name="User_ID" id="User_ID" placeholder="00-0000" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Form_Group_New">
|
||||
<label class="Label_New" for="Email">Email Account</label>
|
||||
<div class="Input_Wrapper">
|
||||
<i class="fas fa-envelope Input_Icon_Left"></i>
|
||||
<input class="Input_New" type="email" name="Email" id="Email" placeholder="firstname.lastname@iloilonhs.edu.ph">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Form_Group_New">
|
||||
<label class="Label_New" for="Password">Password</label>
|
||||
<div class="Input_Wrapper">
|
||||
<i class="fas fa-lock Input_Icon_Left"></i>
|
||||
<input class="Input_New" type="password" name="Password" id="Password" placeholder="Enter your password" required>
|
||||
<button type="button" id="Toggle_Password" class="Btn_Password_Toggle">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="User_Type" id="User_Type_Hidden" value="Voter">
|
||||
|
||||
<button type="submit" class="Btn_Login_Submit">
|
||||
<i class="fas fa-sign-in-alt"></i> LOGIN
|
||||
</button>
|
||||
|
||||
<a href="#" class="Forgot_Password">Forgot Password?</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const loginModal = document.getElementById('Login_Modal');
|
||||
const openLoginBtn = document.getElementById('Open_Login_Btn');
|
||||
const closeLoginBtn = document.getElementById('Close_Login_Btn');
|
||||
const userTypeSelect = document.getElementById('User_Type_Select');
|
||||
const userTypeHidden = document.getElementById('User_Type_Hidden');
|
||||
const togglePassword = document.getElementById('Toggle_Password');
|
||||
const passwordInput = document.getElementById('Password');
|
||||
|
||||
openLoginBtn.addEventListener('click', () => {
|
||||
loginModal.classList.add('Active');
|
||||
});
|
||||
|
||||
closeLoginBtn.addEventListener('click', () => {
|
||||
loginModal.classList.remove('Active');
|
||||
});
|
||||
|
||||
// Close modal when clicking outside the card
|
||||
loginModal.addEventListener('click', (e) => {
|
||||
if (e.target === loginModal) {
|
||||
loginModal.classList.remove('Active');
|
||||
}
|
||||
});
|
||||
|
||||
// Sync Select to Hidden Input (if backend expects specific User_Type field)
|
||||
userTypeSelect.addEventListener('change', (e) => {
|
||||
userTypeHidden.value = e.target.value;
|
||||
});
|
||||
|
||||
// Toggle Password Visibility
|
||||
togglePassword.addEventListener('click', () => {
|
||||
const type = passwordInput.getAttribute('type') === 'password' ? 'text' : 'password';
|
||||
passwordInput.setAttribute('type', type);
|
||||
togglePassword.querySelector('i').classList.toggle('fa-eye');
|
||||
togglePassword.querySelector('i').classList.toggle('fa-eye-slash');
|
||||
});
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.has('error')) {
|
||||
loginModal.classList.add('Active');
|
||||
const errorAlert = document.getElementById('Error_Alert');
|
||||
const errorMessage = document.getElementById('Error_Message');
|
||||
errorAlert.style.display = 'block';
|
||||
errorMessage.textContent = urlParams.get('error');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
4
Screens/Logout.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../Logic/Backend/Authentication_Handler.php';
|
||||
$Auth = new Authentication_Handler();
|
||||
$Auth->Logout();
|
||||
490
Screens/Officers.php
Normal file
@ -0,0 +1,490 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../Logic/Backend/Authentication_Handler.php';
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
$Auth = new Authentication_Handler();
|
||||
$Auth->Check_Auth();
|
||||
|
||||
// Only Admins can access Officer Management (Access Level 3)
|
||||
if ($_SESSION['Access_Level'] < 3) {
|
||||
header('Location: Election_Dashboard.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = db();
|
||||
|
||||
// Fetch Officers grouped by role
|
||||
$admins = $db->query("SELECT * FROM Officers WHERE Role = 'Admin' ORDER BY Name ASC")->fetchAll();
|
||||
$advisers = $db->query("SELECT * FROM Officers WHERE Role = 'COMEA Adviser' ORDER BY Name ASC")->fetchAll();
|
||||
$comeaOfficers = $db->query("SELECT * FROM Officers WHERE Role = 'COMEA Officer' ORDER BY Name ASC")->fetchAll();
|
||||
|
||||
// Get counts for stats
|
||||
$totalAdmins = count($admins);
|
||||
$totalAdvisers = count($advisers);
|
||||
$totalOfficers = count($comeaOfficers);
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Officer Management | Online School Election System</title>
|
||||
<link rel="stylesheet" href="../Design/Style.css?v=<?php echo time(); ?>">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
.Officer_Card_Container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 24px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.Officer_Role_Card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--Border_Color);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.Role_Card_Header {
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid var(--Border_Color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.Role_Card_Title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.Role_Icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.Icon_Admin { background: #EEF2FF; color: #4F46E5; }
|
||||
.Icon_Adviser { background: #FFF7ED; color: #EA580C; }
|
||||
.Icon_Officer { background: #ECFDF5; color: #059669; }
|
||||
|
||||
.Officer_List {
|
||||
padding: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
.Officer_Item {
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.Officer_Item:hover {
|
||||
background: var(--Background_Color);
|
||||
}
|
||||
.Officer_Avatar {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
background: var(--Primary_Light);
|
||||
color: var(--Primary_Color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
}
|
||||
.Officer_Info {
|
||||
flex: 1;
|
||||
}
|
||||
.Officer_Name {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.Officer_Email {
|
||||
font-size: 0.75rem;
|
||||
color: var(--Text_Secondary);
|
||||
}
|
||||
.Officer_Actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
.Officer_Action_Btn {
|
||||
padding: 8px;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--Text_Secondary);
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.Officer_Action_Btn:hover {
|
||||
background: #F1F5F9;
|
||||
color: var(--Primary_Color);
|
||||
}
|
||||
.Btn_Delete:hover {
|
||||
background: #FEE2E2;
|
||||
color: var(--Error_Color);
|
||||
}
|
||||
|
||||
.Add_Section {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
border: 1px solid var(--Border_Color);
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.Form_Grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.Modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 1000;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.Modal_Content {
|
||||
background: white;
|
||||
padding: 32px;
|
||||
border-radius: 20px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
position: relative;
|
||||
box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1);
|
||||
}
|
||||
.Modal_Header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.Modal_Title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.Modal_Close {
|
||||
cursor: pointer;
|
||||
font-size: 1.25rem;
|
||||
color: var(--Text_Secondary);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="Dashboard_Container">
|
||||
<!-- Sidebar -->
|
||||
<aside class="Sidebar">
|
||||
<div class="Sidebar_Header">
|
||||
<div class="Logo_Text">
|
||||
<div class="Logo_Title" style="font-size: 1.1rem; color: var(--Primary_Color);">Click to Vote</div>
|
||||
<div class="Logo_Subtitle">Administrator Portal</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="Sidebar_Nav">
|
||||
<a href="Election_Dashboard.php" class="Nav_Item">
|
||||
<i class="fas fa-th-large"></i> Election Dashboard
|
||||
</a>
|
||||
<a href="Election_History.php" class="Nav_Item">
|
||||
<i class="fas fa-vote-yea"></i> Election History
|
||||
</a>
|
||||
<a href="Voters.php" class="Nav_Item">
|
||||
<i class="fas fa-users"></i> Voter Management
|
||||
</a>
|
||||
<a href="Candidates.php" class="Nav_Item">
|
||||
<i class="fas fa-user-tie"></i> Candidate Management
|
||||
</a>
|
||||
<a href="Officers.php" class="Nav_Item Active">
|
||||
<i class="fas fa-user-shield"></i> Officers Management
|
||||
</a>
|
||||
<a href="Audit_Trail.php" class="Nav_Item">
|
||||
<i class="fas fa-file-alt"></i> Reports & Audit
|
||||
</a>
|
||||
<a href="Credits.php" class="Nav_Item">
|
||||
<i class="fas fa-info-circle"></i> Credits
|
||||
</a>
|
||||
</nav>
|
||||
<div style="padding: 24px; border-top: 1px solid var(--Border_Color);">
|
||||
<a href="Logout.php" class="Nav_Item" style="padding: 0; color: var(--Error_Color);">
|
||||
<i class="fas fa-sign-out-alt"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="Main_Content">
|
||||
<!-- Top Bar -->
|
||||
<header class="Top_Bar">
|
||||
<div class="Search_Box">
|
||||
<i class="fas fa-search" style="color: var(--Secondary_Color);"></i>
|
||||
<input type="text" placeholder="Search officers...">
|
||||
</div>
|
||||
<div class="Top_Bar_Actions">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div style="text-align: right;">
|
||||
<div style="font-weight: 700; font-size: 0.9rem;"><?php echo htmlspecialchars($_SESSION['User_Name']); ?></div>
|
||||
<div style="font-size: 0.75rem; color: var(--Text_Secondary);"><?php echo htmlspecialchars($_SESSION['User_Role']); ?></div>
|
||||
</div>
|
||||
<div style="width: 40px; height: 40px; background: var(--Primary_Light); color: var(--Primary_Color); border-radius: 50%; display: flex; justify-content: center; align-items: center; font-weight: 700;">
|
||||
<?php echo strtoupper(substr($_SESSION['User_Name'] ?? 'A', 0, 1)); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Content Body -->
|
||||
<div class="Content_Body">
|
||||
<?php if (isset($_GET['success'])): ?>
|
||||
<div class="Badge Badge_Success" style="width: 100%; padding: 15px; margin-bottom: 20px; text-align: center;">
|
||||
<i class="fas fa-check-circle"></i> <?php echo htmlspecialchars($_GET['success']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($_GET['error'])): ?>
|
||||
<div class="Badge" style="width: 100%; padding: 15px; margin-bottom: 20px; text-align: center; background: #FEE2E2; color: #EF4444;">
|
||||
<i class="fas fa-exclamation-circle"></i> <?php echo htmlspecialchars($_GET['error']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="Page_Title_Section">
|
||||
<div style="display: flex; align-items: center; gap: 15px;">
|
||||
<div style="width: 48px; height: 48px; background: var(--Primary_Light); color: var(--Primary_Color); border-radius: 12px; display: flex; justify-content: center; align-items: center; font-size: 1.5rem;">
|
||||
<i class="fas fa-user-shield"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="Page_Title">Officer Management</h1>
|
||||
<p class="Page_Subtitle">Manage system administrators and COMEA personnel</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Registration Form -->
|
||||
<div class="Add_Section">
|
||||
<h2 class="Card_Title" style="margin-bottom: 20px;"><i class="fas fa-plus-circle" style="color: var(--Primary_Color);"></i> Register New Officer</h2>
|
||||
<form action="../Logic/Backend/Add_Officer_Handler.php" method="POST">
|
||||
<div class="Form_Grid">
|
||||
<div class="Form_Group">
|
||||
<label class="Label">Full Name</label>
|
||||
<input type="text" name="name" class="Input" placeholder="Enter name" required>
|
||||
</div>
|
||||
<div class="Form_Group">
|
||||
<label class="Label">Email</label>
|
||||
<input type="email" name="email" class="Input" placeholder="email@school.edu" required>
|
||||
</div>
|
||||
<div class="Form_Group">
|
||||
<label class="Label">User ID</label>
|
||||
<input type="text" name="user_id" class="Input" placeholder="Leave blank for random">
|
||||
</div>
|
||||
<div class="Form_Group">
|
||||
<label class="Label">Role</label>
|
||||
<select name="role" class="Select" style="width: 100%; height: 50px;" required>
|
||||
<option value="Admin">Admin</option>
|
||||
<option value="COMEA Adviser">COMEA Adviser</option>
|
||||
<option value="COMEA Officer">COMEA Officer</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="Form_Group">
|
||||
<label class="Label">Password</label>
|
||||
<input type="password" name="password" class="Input" placeholder="Leave blank for default">
|
||||
</div>
|
||||
<div class="Form_Group" style="display: flex; align-items: flex-end;">
|
||||
<button type="submit" class="Button_Primary" style="margin-top: 0; width: 100%;">
|
||||
<i class="fas fa-save"></i> Save Officer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Roles Cards -->
|
||||
<div class="Officer_Card_Container">
|
||||
<!-- Admin Card -->
|
||||
<div class="Officer_Role_Card">
|
||||
<div class="Role_Card_Header">
|
||||
<div class="Role_Card_Title">
|
||||
<div class="Role_Icon Icon_Admin"><i class="fas fa-user-cog"></i></div>
|
||||
Admins
|
||||
</div>
|
||||
<span class="Badge Badge_Success"><?php echo $totalAdmins; ?> Active</span>
|
||||
</div>
|
||||
<div class="Officer_List">
|
||||
<?php foreach ($admins as $admin): ?>
|
||||
<div class="Officer_Item">
|
||||
<div class="Officer_Avatar">
|
||||
<?php if (!empty($admin['Photo'])): ?>
|
||||
<img src="<?php echo htmlspecialchars($admin['Photo']); ?>" alt="" style="width: 100%; height: 100%; object-fit: cover;">
|
||||
<?php else: ?>
|
||||
<?php echo strtoupper(substr($admin['Name'], 0, 1)); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="Officer_Info">
|
||||
<div class="Officer_Name"><?php echo htmlspecialchars($admin['Name']); ?></div>
|
||||
<div class="Officer_Email"><?php echo htmlspecialchars($admin['User_ID']); ?> | <?php echo htmlspecialchars($admin['Email']); ?></div>
|
||||
</div>
|
||||
<div class="Officer_Actions">
|
||||
<button class="Officer_Action_Btn" title="Edit" onclick='openEditModal(<?php echo json_encode($admin); ?>)'><i class="fas fa-edit"></i></button>
|
||||
<?php if ($admin['User_ID'] !== $_SESSION['User_ID']): ?>
|
||||
<button class="Officer_Action_Btn Btn_Delete" title="Delete" onclick="deleteOfficer('<?php echo $admin['User_ID']; ?>', '<?php echo htmlspecialchars($admin['Name']); ?>')"><i class="fas fa-trash-alt"></i></button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Adviser Card -->
|
||||
<div class="Officer_Role_Card">
|
||||
<div class="Role_Card_Header">
|
||||
<div class="Role_Card_Title">
|
||||
<div class="Role_Icon Icon_Adviser"><i class="fas fa-user-tie"></i></div>
|
||||
COMEA Advisers
|
||||
</div>
|
||||
<span class="Badge" style="background: #FFF7ED; color: #EA580C;"><?php echo $totalAdvisers; ?> Active</span>
|
||||
</div>
|
||||
<div class="Officer_List">
|
||||
<?php if (empty($advisers)): ?>
|
||||
<p style="padding: 24px; text-align: center; color: var(--Text_Secondary); font-size: 0.85rem;">No advisers registered yet.</p>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($advisers as $adviser): ?>
|
||||
<div class="Officer_Item">
|
||||
<div class="Officer_Avatar">
|
||||
<?php if (!empty($adviser['Photo'])): ?>
|
||||
<img src="<?php echo htmlspecialchars($adviser['Photo']); ?>" alt="" style="width: 100%; height: 100%; object-fit: cover;">
|
||||
<?php else: ?>
|
||||
<?php echo strtoupper(substr($adviser['Name'], 0, 1)); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="Officer_Info">
|
||||
<div class="Officer_Name"><?php echo htmlspecialchars($adviser['Name']); ?></div>
|
||||
<div class="Officer_Email"><?php echo htmlspecialchars($adviser['User_ID']); ?> | <?php echo htmlspecialchars($adviser['Email']); ?></div>
|
||||
</div>
|
||||
<div class="Officer_Actions">
|
||||
<button class="Officer_Action_Btn" title="Edit" onclick='openEditModal(<?php echo json_encode($adviser); ?>)'><i class="fas fa-edit"></i></button>
|
||||
<button class="Officer_Action_Btn Btn_Delete" title="Delete" onclick="deleteOfficer('<?php echo $adviser['User_ID']; ?>', '<?php echo htmlspecialchars($adviser['Name']); ?>')"><i class="fas fa-trash-alt"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Officer Card -->
|
||||
<div class="Officer_Role_Card">
|
||||
<div class="Role_Card_Header">
|
||||
<div class="Role_Card_Title">
|
||||
<div class="Role_Icon Icon_Officer"><i class="fas fa-user-shield"></i></div>
|
||||
COMEA Officers
|
||||
</div>
|
||||
<span class="Badge" style="background: #ECFDF5; color: #059669;"><?php echo $totalOfficers; ?> Active</span>
|
||||
</div>
|
||||
<div class="Officer_List">
|
||||
<?php if (empty($comeaOfficers)): ?>
|
||||
<p style="padding: 24px; text-align: center; color: var(--Text_Secondary); font-size: 0.85rem;">No officers registered yet.</p>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($comeaOfficers as $officer): ?>
|
||||
<div class="Officer_Item">
|
||||
<div class="Officer_Avatar">
|
||||
<?php if (!empty($officer['Photo'])): ?>
|
||||
<img src="<?php echo htmlspecialchars($officer['Photo']); ?>" alt="" style="width: 100%; height: 100%; object-fit: cover;">
|
||||
<?php else: ?>
|
||||
<?php echo strtoupper(substr($officer['Name'], 0, 1)); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="Officer_Info">
|
||||
<div class="Officer_Name"><?php echo htmlspecialchars($officer['Name']); ?></div>
|
||||
<div class="Officer_Email"><?php echo htmlspecialchars($officer['User_ID']); ?> | <?php echo htmlspecialchars($officer['Email']); ?></div>
|
||||
</div>
|
||||
<div class="Officer_Actions">
|
||||
<button class="Officer_Action_Btn" title="Edit" onclick='openEditModal(<?php echo json_encode($officer); ?>)'><i class="fas fa-edit"></i></button>
|
||||
<button class="Officer_Action_Btn Btn_Delete" title="Delete" onclick="deleteOfficer('<?php echo $officer['User_ID']; ?>', '<?php echo htmlspecialchars($officer['Name']); ?>')"><i class="fas fa-trash-alt"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div id="EditModal" class="Modal">
|
||||
<div class="Modal_Content">
|
||||
<div class="Modal_Header">
|
||||
<h2 class="Modal_Title">Edit Officer</h2>
|
||||
<span class="Modal_Close" onclick="closeModal()">×</span>
|
||||
</div>
|
||||
<form action="../Logic/Backend/Edit_Officer_Handler.php" method="POST">
|
||||
<input type="hidden" name="original_user_id" id="edit_original_id">
|
||||
<div class="Form_Group">
|
||||
<label class="Label">Full Name</label>
|
||||
<input type="text" name="name" id="edit_name" class="Input" required>
|
||||
</div>
|
||||
<div class="Form_Group" style="margin-top: 15px;">
|
||||
<label class="Label">Email</label>
|
||||
<input type="email" name="email" id="edit_email" class="Input" required>
|
||||
</div>
|
||||
<div class="Form_Group" style="margin-top: 15px;">
|
||||
<label class="Label">User ID</label>
|
||||
<input type="text" name="user_id" id="edit_user_id" class="Input" required>
|
||||
</div>
|
||||
<div class="Form_Group" style="margin-top: 15px;">
|
||||
<label class="Label">Role</label>
|
||||
<select name="role" id="edit_role" class="Select" style="width: 100%; height: 50px;" required>
|
||||
<option value="Admin">Admin</option>
|
||||
<option value="COMEA Adviser">COMEA Adviser</option>
|
||||
<option value="COMEA Officer">COMEA Officer</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="Form_Group" style="margin-top: 15px;">
|
||||
<label class="Label">New Password</label>
|
||||
<input type="password" name="password" class="Input" placeholder="Leave blank to keep current">
|
||||
</div>
|
||||
<button type="submit" class="Button_Primary" style="width: 100%; margin-top: 24px;">Update Officer</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openEditModal(officer) {
|
||||
document.getElementById('edit_original_id').value = officer.User_ID;
|
||||
document.getElementById('edit_user_id').value = officer.User_ID;
|
||||
document.getElementById('edit_name').value = officer.Name;
|
||||
document.getElementById('edit_email').value = officer.Email;
|
||||
document.getElementById('edit_role').value = officer.Role;
|
||||
document.getElementById('EditModal').style.display = 'flex';
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('EditModal').style.display = 'none';
|
||||
}
|
||||
|
||||
function deleteOfficer(id, name) {
|
||||
if (confirm(`Are you sure you want to delete officer ${name} (${id})? This action cannot be undone.`)) {
|
||||
window.location.href = `../Logic/Backend/Delete_Officer_Handler.php?user_id=${id}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Close modal when clicking outside
|
||||
window.onclick = function(event) {
|
||||
if (event.target == document.getElementById('EditModal')) {
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
439
Screens/Voters.php
Normal file
@ -0,0 +1,439 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../Logic/Backend/Authentication_Handler.php';
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
$Auth = new Authentication_Handler();
|
||||
$Auth->Check_Auth();
|
||||
|
||||
if ($_SESSION['Access_Level'] < 1) {
|
||||
header('Location: Voting_Screen.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = db();
|
||||
|
||||
// Filters
|
||||
$search = $_GET['search'] ?? '';
|
||||
$filterTrack = $_GET['track'] ?? '';
|
||||
$filterGrade = $_GET['grade'] ?? '';
|
||||
$filterSection = $_GET['section'] ?? '';
|
||||
|
||||
// Fetch Stats
|
||||
$totalVoters = $db->query("SELECT COUNT(*) FROM Voters")->fetchColumn();
|
||||
$votedCount = $db->query("SELECT COUNT(*) FROM Voters WHERE Has_Voted = 1")->fetchColumn();
|
||||
$notVotedCount = $totalVoters - $votedCount;
|
||||
|
||||
// Fetch Breakdowns
|
||||
$tracks = $db->query("SELECT Track_Cluster as label, COUNT(*) as value FROM Voters GROUP BY Track_Cluster")->fetchAll();
|
||||
$grades = $db->query("SELECT Grade_Level as label, COUNT(*) as value FROM Voters GROUP BY Grade_Level")->fetchAll();
|
||||
$sections = $db->query("SELECT Section as label, COUNT(*) as value FROM Voters GROUP BY Section")->fetchAll();
|
||||
|
||||
// Build Query for List
|
||||
$queryStr = "SELECT * FROM Voters WHERE 1=1";
|
||||
$params = [];
|
||||
|
||||
if ($search) {
|
||||
$queryStr .= " AND Email LIKE ?";
|
||||
$params[] = "%$search%";
|
||||
}
|
||||
if ($filterTrack) {
|
||||
$queryStr .= " AND Track_Cluster = ?";
|
||||
$params[] = $filterTrack;
|
||||
}
|
||||
if ($filterGrade) {
|
||||
$queryStr .= " AND Grade_Level = ?";
|
||||
$params[] = $filterGrade;
|
||||
}
|
||||
if ($filterSection) {
|
||||
$queryStr .= " AND Section = ?";
|
||||
$params[] = $filterSection;
|
||||
}
|
||||
|
||||
$queryStr .= " ORDER BY Email ASC";
|
||||
$stmt = $db->prepare($queryStr);
|
||||
$stmt->execute($params);
|
||||
$votersList = $stmt->fetchAll();
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Voter Management | Online School Election System</title>
|
||||
<link rel="stylesheet" href="../Design/Style.css?v=<?php echo time(); ?>">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
.Voter_Actions_Btn {
|
||||
height: 44px;
|
||||
padding: 0 24px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.Modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 1000;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.Modal_Content {
|
||||
background: white;
|
||||
padding: 32px;
|
||||
border-radius: 20px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
position: relative;
|
||||
box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1);
|
||||
}
|
||||
.Modal_Header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.Modal_Title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.Modal_Close {
|
||||
cursor: pointer;
|
||||
font-size: 1.25rem;
|
||||
color: var(--Text_Secondary);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="Dashboard_Container">
|
||||
<!-- Sidebar -->
|
||||
<aside class="Sidebar">
|
||||
<div class="Sidebar_Header">
|
||||
<div class="Logo_Text">
|
||||
<div class="Logo_Title" style="font-size: 1.1rem; color: var(--Primary_Color);">Click to Vote</div>
|
||||
<div class="Logo_Subtitle">Administrator Portal</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="Sidebar_Nav">
|
||||
<a href="Election_Dashboard.php" class="Nav_Item">
|
||||
<i class="fas fa-th-large"></i> Election Dashboard
|
||||
</a>
|
||||
<a href="Election_History.php" class="Nav_Item">
|
||||
<i class="fas fa-vote-yea"></i> Election History
|
||||
</a>
|
||||
<a href="Voters.php" class="Nav_Item Active">
|
||||
<i class="fas fa-users"></i> Voter Management
|
||||
</a>
|
||||
<a href="Candidates.php" class="Nav_Item">
|
||||
<i class="fas fa-user-tie"></i> Candidate Management
|
||||
</a>
|
||||
<a href="Officers.php" class="Nav_Item">
|
||||
<i class="fas fa-user-shield"></i> Officers Management
|
||||
</a>
|
||||
<a href="Audit_Trail.php" class="Nav_Item">
|
||||
<i class="fas fa-file-alt"></i> Reports & Audit
|
||||
</a>
|
||||
<a href="Credits.php" class="Nav_Item">
|
||||
<i class="fas fa-info-circle"></i> Credits
|
||||
</a>
|
||||
</nav>
|
||||
<div style="padding: 24px; border-top: 1px solid var(--Border_Color);">
|
||||
<a href="Logout.php" class="Nav_Item" style="padding: 0; color: var(--Error_Color);">
|
||||
<i class="fas fa-sign-out-alt"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="Main_Content">
|
||||
<!-- Top Bar -->
|
||||
<header class="Top_Bar">
|
||||
<div class="Search_Box">
|
||||
<i class="fas fa-search" style="color: var(--Secondary_Color);"></i>
|
||||
<input type="text" placeholder="Quick search...">
|
||||
</div>
|
||||
<div class="Top_Bar_Actions">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div style="text-align: right;">
|
||||
<div style="font-weight: 700; font-size: 0.9rem;"><?php echo htmlspecialchars($_SESSION['User_Name']); ?></div>
|
||||
<div style="font-size: 0.75rem; color: var(--Text_Secondary);"><?php echo htmlspecialchars($_SESSION['User_Role']); ?></div>
|
||||
</div>
|
||||
<div style="width: 40px; height: 40px; background: var(--Primary_Light); color: var(--Primary_Color); border-radius: 50%; display: flex; justify-content: center; align-items: center; font-weight: 700;">
|
||||
<?php echo strtoupper(substr($_SESSION['User_Name'] ?? 'A', 0, 1)); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Content Body -->
|
||||
<div class="Content_Body">
|
||||
<?php if (isset($_GET['success'])): ?>
|
||||
<div class="Badge Badge_Success" style="width: 100%; padding: 15px; margin-bottom: 20px; text-align: center;">
|
||||
<i class="fas fa-check-circle"></i> <?php echo htmlspecialchars($_GET['success']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($_GET['error'])): ?>
|
||||
<div class="Badge" style="width: 100%; padding: 15px; margin-bottom: 20px; text-align: center; background: #FEE2E2; color: #EF4444;">
|
||||
<i class="fas fa-exclamation-circle"></i> <?php echo htmlspecialchars($_GET['error']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="Page_Title_Section">
|
||||
<div style="display: flex; align-items: center; gap: 15px;">
|
||||
<div style="width: 48px; height: 48px; background: var(--Primary_Light); color: var(--Primary_Color); border-radius: 12px; display: flex; justify-content: center; align-items: center; font-size: 1.5rem;">
|
||||
<i class="fas fa-user-check"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="Page_Title">Voters List</h1>
|
||||
<p class="Page_Subtitle">View and manage registered voters</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Primary Stats -->
|
||||
<div class="Stat_Cards_Grid" style="margin-bottom: 20px;">
|
||||
<div class="Stat_Card">
|
||||
<div class="Stat_Label" style="text-transform: uppercase; font-size: 0.7rem; font-weight: 700;">Total Voters</div>
|
||||
<div class="Stat_Value" style="color: var(--Primary_Color); font-size: 2.5rem; margin-top: 10px;"><?php echo $totalVoters; ?></div>
|
||||
</div>
|
||||
<div class="Stat_Card">
|
||||
<div class="Stat_Label" style="text-transform: uppercase; font-size: 0.7rem; font-weight: 700; color: var(--Secondary_Color);">Voters Who Voted</div>
|
||||
<div class="Stat_Value" style="color: var(--Secondary_Color); font-size: 2.5rem; margin-top: 10px;"><?php echo $votedCount; ?></div>
|
||||
</div>
|
||||
<div class="Stat_Card">
|
||||
<div class="Stat_Label" style="text-transform: uppercase; font-size: 0.7rem; font-weight: 700; color: var(--Error_Color);">Voters Who Haven't Voted</div>
|
||||
<div class="Stat_Value" style="color: var(--Error_Color); font-size: 2.5rem; margin-top: 10px;"><?php echo $notVotedCount; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Breakdown Stats -->
|
||||
<div class="Summary_Breakdowns">
|
||||
<div class="Breakdown_Card">
|
||||
<h3 class="Breakdown_Title">Total by Track</h3>
|
||||
<?php foreach ($tracks as $track): ?>
|
||||
<div class="Breakdown_Item">
|
||||
<span><?php echo htmlspecialchars($track['label'] ?: 'N/A'); ?></span>
|
||||
<span style="font-weight: 700;"><?php echo $track['value']; ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="Breakdown_Card">
|
||||
<h3 class="Breakdown_Title">Total by Grade</h3>
|
||||
<?php foreach ($grades as $grade): ?>
|
||||
<div class="Breakdown_Item">
|
||||
<span>Grade <?php echo htmlspecialchars($grade['label'] ?: 'N/A'); ?></span>
|
||||
<span style="font-weight: 700;"><?php echo $grade['value']; ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="Breakdown_Card">
|
||||
<h3 class="Breakdown_Title">Total by Section</h3>
|
||||
<?php foreach (array_slice($sections, 0, 5) as $section): ?>
|
||||
<div class="Breakdown_Item">
|
||||
<span><?php echo htmlspecialchars($section['label'] ?: 'N/A'); ?></span>
|
||||
<span style="font-weight: 700;"><?php echo $section['value']; ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php if (count($sections) > 5): ?>
|
||||
<div style="font-size: 0.75rem; color: var(--Text_Secondary); text-align: center; margin-top: 10px;">+ <?php echo count($sections) - 5; ?> more sections</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div style="display: flex; justify-content: flex-end; gap: 12px; margin-bottom: 20px;">
|
||||
<button class="Button_Primary Voter_Actions_Btn" onclick="openAddModal()">
|
||||
<i class="fas fa-plus"></i> Add Voter
|
||||
</button>
|
||||
<button class="Button_Secondary Voter_Actions_Btn" onclick="openImportModal()">
|
||||
<i class="fas fa-file-import"></i> Import CSV
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Table Card -->
|
||||
<div class="Card" style="padding: 0; overflow: hidden;">
|
||||
<!-- Filters (Integrated into Card with Flex Layout) -->
|
||||
<form method="GET" class="Filter_Bar" style="display: flex; flex-wrap: nowrap; gap: 20px; align-items: flex-end; background: transparent; border: none; border-bottom: 1px solid var(--Border_Color); border-radius: 0; margin-bottom: 0; padding: 24px;">
|
||||
<div class="Filter_Group" style="flex: 2;">
|
||||
<label>Search</label>
|
||||
<div class="Search_Input_Wrapper">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" name="search" class="Input" placeholder="Search by email" value="<?php echo htmlspecialchars($search); ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="Filter_Group" style="flex: 1;">
|
||||
<label>Track</label>
|
||||
<select name="track" class="Select" onchange="this.form.submit()" style="width: 100%;">
|
||||
<option value="">All Tracks</option>
|
||||
<?php
|
||||
$allTracks = $db->query("SELECT DISTINCT Track_Cluster FROM Voters WHERE Track_Cluster IS NOT NULL")->fetchAll(PDO::FETCH_COLUMN);
|
||||
foreach ($allTracks as $t): ?>
|
||||
<option value="<?php echo htmlspecialchars($t); ?>" <?php echo $filterTrack == $t ? 'selected' : ''; ?>><?php echo htmlspecialchars($t); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="Filter_Group" style="flex: 1;">
|
||||
<label>Grade</label>
|
||||
<select name="grade" class="Select" onchange="this.form.submit()" style="width: 100%;">
|
||||
<option value="">All Grades</option>
|
||||
<?php
|
||||
$allGrades = $db->query("SELECT DISTINCT Grade_Level FROM Voters WHERE Grade_Level IS NOT NULL ORDER BY Grade_Level")->fetchAll(PDO::FETCH_COLUMN);
|
||||
foreach ($allGrades as $g): ?>
|
||||
<option value="<?php echo htmlspecialchars($g); ?>" <?php echo $filterGrade == $g ? 'selected' : ''; ?>>Grade <?php echo htmlspecialchars($g); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="Filter_Group" style="flex: 1;">
|
||||
<label>Section</label>
|
||||
<select name="section" class="Select" onchange="this.form.submit()" style="width: 100%;">
|
||||
<option value="">All Sections</option>
|
||||
<?php
|
||||
$allSections = $db->query("SELECT DISTINCT Section FROM Voters WHERE Section IS NOT NULL ORDER BY Section")->fetchAll(PDO::FETCH_COLUMN);
|
||||
foreach ($allSections as $s): ?>
|
||||
<option value="<?php echo htmlspecialchars($s); ?>" <?php echo $filterSection == $s ? 'selected' : ''; ?>><?php echo htmlspecialchars($s); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="Table_Wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User ID</th>
|
||||
<th>Email</th>
|
||||
<th>Track</th>
|
||||
<th>Grade</th>
|
||||
<th>Section</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($votersList)): ?>
|
||||
<tr>
|
||||
<td colspan="6" style="text-align: center; padding: 40px; color: var(--Text_Secondary);">
|
||||
No voters found matching your criteria.
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($votersList as $voter): ?>
|
||||
<tr>
|
||||
<td style="font-weight: 600;"><?php echo htmlspecialchars($voter['User_ID']); ?></td>
|
||||
<td><?php echo htmlspecialchars($voter['Email']); ?></td>
|
||||
<td><?php echo htmlspecialchars($voter['Track_Cluster'] ?: 'N/A'); ?></td>
|
||||
<td>Grade <?php echo htmlspecialchars($voter['Grade_Level'] ?: 'N/A'); ?></td>
|
||||
<td><?php echo htmlspecialchars($voter['Section'] ?: 'N/A'); ?></td>
|
||||
<td>
|
||||
<?php if ($voter['Has_Voted']): ?>
|
||||
<span class="Badge Badge_Success">Voted</span>
|
||||
<?php else: ?>
|
||||
<span class="Badge" style="background: var(--Primary_Light); color: var(--Primary_Color);">Pending</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="padding: 15px 24px; font-size: 0.85rem; color: var(--Text_Secondary); border-top: 1px solid var(--Border_Color);">
|
||||
Showing <?php echo count($votersList); ?> of <?php echo $totalVoters; ?> voters
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Add Voter Modal -->
|
||||
<div id="AddModal" class="Modal">
|
||||
<div class="Modal_Content">
|
||||
<div class="Modal_Header">
|
||||
<h2 class="Modal_Title">Add New Voter</h2>
|
||||
<span class="Modal_Close" onclick="closeModal('AddModal')">×</span>
|
||||
</div>
|
||||
<form action="../Logic/Backend/Add_Voter_Handler.php" method="POST">
|
||||
<div class="Form_Group">
|
||||
<label class="Label">Email Address</label>
|
||||
<input type="email" name="email" class="Input" placeholder="voter@school.edu" required>
|
||||
</div>
|
||||
<div class="Form_Group" style="margin-top: 15px;">
|
||||
<label class="Label">User ID</label>
|
||||
<input type="text" name="user_id" class="Input" placeholder="Leave blank for random">
|
||||
</div>
|
||||
<div class="Form_Group" style="margin-top: 15px;">
|
||||
<label class="Label">Track / Strand</label>
|
||||
<input type="text" name="track" class="Input" placeholder="e.g. STEM" required>
|
||||
</div>
|
||||
<div class="Form_Group" style="margin-top: 15px;">
|
||||
<label class="Label">Grade Level</label>
|
||||
<select name="grade" class="Select" style="width: 100%; height: 50px;" required>
|
||||
<option value="11">Grade 11</option>
|
||||
<option value="12">Grade 12</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="Form_Group" style="margin-top: 15px;">
|
||||
<label class="Label">Section</label>
|
||||
<input type="text" name="section" class="Input" placeholder="e.g. A" required>
|
||||
</div>
|
||||
<div class="Form_Group" style="margin-top: 15px;">
|
||||
<label class="Label">Password</label>
|
||||
<input type="password" name="password" class="Input" placeholder="Leave blank for default">
|
||||
</div>
|
||||
<button type="submit" class="Button_Primary" style="width: 100%; margin-top: 24px;">Save Voter</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import CSV Modal -->
|
||||
<div id="ImportModal" class="Modal">
|
||||
<div class="Modal_Content">
|
||||
<div class="Modal_Header">
|
||||
<h2 class="Modal_Title">Import Voters via CSV</h2>
|
||||
<span class="Modal_Close" onclick="closeModal('ImportModal')">×</span>
|
||||
</div>
|
||||
<p class="Text_Muted" style="margin-bottom: 20px; font-size: 0.85rem;">
|
||||
Upload a CSV file with the following headers: <br>
|
||||
<strong>User_ID, Email, Track_Cluster, Grade_Level, Section, Password</strong>
|
||||
</p>
|
||||
<form action="../Logic/Backend/Import_Voters_Handler.php" method="POST" enctype="multipart/form-data">
|
||||
<div class="Form_Group">
|
||||
<label class="Label">Select CSV File</label>
|
||||
<input type="file" name="csv_file" class="Input" accept=".csv" required>
|
||||
</div>
|
||||
<button type="submit" class="Button_Primary" style="width: 100%; margin-top: 24px;">Upload & Import</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openAddModal() {
|
||||
document.getElementById('AddModal').style.display = 'flex';
|
||||
}
|
||||
function openImportModal() {
|
||||
document.getElementById('ImportModal').style.display = 'flex';
|
||||
}
|
||||
function closeModal(id) {
|
||||
document.getElementById(id).style.display = 'none';
|
||||
}
|
||||
|
||||
// Close modal when clicking outside
|
||||
window.onclick = function(event) {
|
||||
if (event.target.classList.contains('Modal')) {
|
||||
event.target.style.display = 'none';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
33
Screens/Voting_Screen.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../Logic/Backend/Authentication_Handler.php';
|
||||
$Auth = new Authentication_Handler();
|
||||
$Auth->Check_Auth();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Voting Screen - Online School Election System</title>
|
||||
<link rel="stylesheet" href="../Design/Style.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="Dashboard_Navbar">
|
||||
<div class="Logo"><strong>Click to Vote</strong></div>
|
||||
<div class="User_Info">
|
||||
<span><?php echo $_SESSION['User_Name']; ?> (Voter)</span>
|
||||
<a href="Logout.php" style="margin-left: 20px; font-size: 0.875rem;">Logout</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main style="padding: 40px 24px; max-width: 800px; margin: 0 auto;">
|
||||
<h2 style="margin-bottom: 24px; text-align: center;">Welcome, <?php echo $_SESSION['User_Name']; ?></h2>
|
||||
|
||||
<div class="Card" style="max-width: 100%; text-align: center;">
|
||||
<div style="font-size: 3rem; margin-bottom: 20px;">🗳️</div>
|
||||
<h3 style="margin-bottom: 12px;">Election Status</h3>
|
||||
<p class="Text_Muted">There is no active election at the moment. Please check back later during the scheduled voting period.</p>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
BIN
assets/pasted-20260204-200027-bc93fd52.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
assets/pasted-20260204-200305-388a4105.jpg
Normal file
|
After Width: | Height: | Size: 408 KiB |
BIN
assets/pasted-20260204-200612-dffb96f0.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
assets/pasted-20260204-200921-0cdb8fcc.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
assets/pasted-20260204-201746-b63fc6e9.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
assets/pasted-20260204-202142-e8ddf1bb.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
assets/pasted-20260204-210904-9c726cb9.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
assets/pasted-20260204-211215-fded7a35.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
151
index.php
@ -1,150 +1,3 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
header('Location: Screens/Login.php');
|
||||
exit;
|
||||
72
init_db.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
$queries = [
|
||||
"CREATE TABLE IF NOT EXISTS Officers (
|
||||
User_ID VARCHAR(255) PRIMARY KEY,
|
||||
Role VARCHAR(100),
|
||||
Access_Level INT DEFAULT 0,
|
||||
Email VARCHAR(255) UNIQUE,
|
||||
Password VARCHAR(255),
|
||||
Name VARCHAR(255),
|
||||
Photo VARCHAR(255)
|
||||
)",
|
||||
"CREATE TABLE IF NOT EXISTS Voters (
|
||||
User_ID VARCHAR(255) PRIMARY KEY,
|
||||
Email VARCHAR(255) UNIQUE,
|
||||
Password VARCHAR(255),
|
||||
Track_Cluster VARCHAR(100),
|
||||
Grade_Level INT,
|
||||
Section VARCHAR(100),
|
||||
Has_Voted BOOLEAN DEFAULT FALSE
|
||||
)",
|
||||
"CREATE TABLE IF NOT EXISTS Candidates (
|
||||
User_ID VARCHAR(255) PRIMARY KEY,
|
||||
Email VARCHAR(255) UNIQUE,
|
||||
Password VARCHAR(255),
|
||||
Track_Cluster VARCHAR(100),
|
||||
Grade_Level INT,
|
||||
Section VARCHAR(100),
|
||||
Name VARCHAR(255),
|
||||
Position VARCHAR(255),
|
||||
Party VARCHAR(255),
|
||||
Photo VARCHAR(255)
|
||||
)",
|
||||
"CREATE TABLE IF NOT EXISTS Election_History (
|
||||
Election_ID INT AUTO_INCREMENT PRIMARY KEY,
|
||||
Year INT,
|
||||
Parties TEXT,
|
||||
Positions TEXT,
|
||||
Candidates TEXT,
|
||||
Results TEXT,
|
||||
Total_Voters INT,
|
||||
Status VARCHAR(50),
|
||||
Start_Date DATETIME,
|
||||
End_Date DATETIME
|
||||
)",
|
||||
"CREATE TABLE IF NOT EXISTS Audit_Trail (
|
||||
Log_ID INT AUTO_INCREMENT PRIMARY KEY,
|
||||
User_ID VARCHAR(255),
|
||||
User_Role VARCHAR(100),
|
||||
Action_Type VARCHAR(100),
|
||||
Action_Details TEXT,
|
||||
Timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)"
|
||||
];
|
||||
|
||||
foreach ($queries as $query) {
|
||||
$pdo->exec($query);
|
||||
}
|
||||
|
||||
// Insert default admin (Password: Admin123)
|
||||
$stmt = $pdo->prepare("INSERT IGNORE INTO Officers (User_ID, Role, Access_Level, Email, Password, Name) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute(['00-0000', 'Admin', 3, 'admin@school.edu', password_hash('Admin123', PASSWORD_DEFAULT), 'System Administrator']);
|
||||
|
||||
echo "Database initialized successfully.\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
die("Error initializing database: " . $e->getMessage());
|
||||
}
|
||||