Fixed assignment logs page to be flatter
This commit is contained in:
@@ -1,10 +1,14 @@
|
|||||||
.logs-container {
|
.logs-container {
|
||||||
max-width: 1200px;
|
max-width: 1400px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs-header {
|
.logs-header {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: start;
|
||||||
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs-header h2 {
|
.logs-header h2 {
|
||||||
@@ -44,7 +48,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
to { transform: rotate(360deg); }
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.retry-btn {
|
.retry-btn {
|
||||||
@@ -62,154 +68,6 @@
|
|||||||
background-color: #6d28d9;
|
background-color: #6d28d9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-card {
|
|
||||||
background-color: #2b2b2b;
|
|
||||||
border: 1px solid #374151;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 1.25rem;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-card:hover {
|
|
||||||
border-color: #7c3aed;
|
|
||||||
box-shadow: 0 4px 12px rgba(124, 58, 237, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-main {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: start;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-info {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-class {
|
|
||||||
color: #d8b4fe;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 1.125rem;
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-assignment {
|
|
||||||
color: #e0e0e0;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-meta {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-category {
|
|
||||||
background-color: rgba(124, 58, 237, 0.2);
|
|
||||||
color: #c4b5fd;
|
|
||||||
padding: 0.125rem 0.5rem;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-separator {
|
|
||||||
color: #4b5563;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-period {
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-grade-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-end;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-grade {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #4ade80;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-change {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.change-positive {
|
|
||||||
background-color: rgba(74, 222, 128, 0.1);
|
|
||||||
color: #4ade80;
|
|
||||||
}
|
|
||||||
|
|
||||||
.change-negative {
|
|
||||||
background-color: rgba(248, 113, 113, 0.1);
|
|
||||||
color: #f87171;
|
|
||||||
}
|
|
||||||
|
|
||||||
.change-none {
|
|
||||||
background-color: rgba(156, 163, 175, 0.1);
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-footer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding-top: 0.75rem;
|
|
||||||
border-top: 1px solid #374151;
|
|
||||||
color: #6b7280;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-timestamp {
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-due-date {
|
|
||||||
color: #fbbf24;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.log-main {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-grade-info {
|
|
||||||
align-items: flex-start;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-grade {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs-header {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: start;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sort-controls {
|
.sort-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -239,11 +97,180 @@
|
|||||||
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1);
|
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-due-date {
|
/* Table-like list */
|
||||||
color: #fbbf24;
|
.logs-list {
|
||||||
font-weight: 500;
|
background-color: #2b2b2b;
|
||||||
|
border: 1px solid #374151;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.log-card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.5fr 2.5fr 0.8fr 1fr 0.8fr 1fr;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-bottom: 1px solid #374151;
|
||||||
|
transition: background-color 0.15s;
|
||||||
|
min-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-card:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-card:hover {
|
||||||
|
background-color: rgba(124, 58, 237, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-class {
|
||||||
|
color: #d8b4fe;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-assignment {
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-category {
|
||||||
|
background-color: rgba(124, 58, 237, 0.2);
|
||||||
|
color: #c4b5fd;
|
||||||
|
padding: 0.125rem 0.375rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-due-date {
|
||||||
|
color: #fbbf24;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-grade {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #4ade80;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-change-timestamp {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-change {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-positive {
|
||||||
|
color: #4ade80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-negative {
|
||||||
|
color: #f87171;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-none {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-timestamp {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pagination */
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-info {
|
||||||
|
color: #9ca3af;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-family: "JetBrains Mono", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn {
|
||||||
|
background-color: #2b2b2b;
|
||||||
|
border: 1px solid #374151;
|
||||||
|
color: #e0e0e0;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-family: "JetBrains Mono", monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn:hover:not(:disabled) {
|
||||||
|
border-color: #7c3aed;
|
||||||
|
background-color: #323232;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn:disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablet */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.log-card {
|
||||||
|
grid-template-columns: 1.2fr 2fr 0.7fr 0.8fr 0.7fr 1fr;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.65rem 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-class,
|
||||||
|
.log-assignment {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-grade {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.logs-header {
|
.logs-header {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -258,4 +285,83 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.log-card {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-class {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-assignment {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin-top: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-meta {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-due-date {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-grade {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
text-align: left;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-change-timestamp {
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-timestamp {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-info {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Small mobile */
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.logs-header h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-card {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-class {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-assignment {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-category {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-grade {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,28 +1,34 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Clock, TrendingUp, TrendingDown, Minus, ArrowUpDown } from 'lucide-react';
|
import { Clock, TrendingUp, TrendingDown, Minus, ArrowUpDown, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
import { api } from '../services/api';
|
import { api } from '../services/api';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
import './AssignmentLogs.css';
|
import './AssignmentLogs.css';
|
||||||
|
|
||||||
|
const ITEMS_PER_PAGE = 20;
|
||||||
|
|
||||||
export const AssignmentLogs = () => {
|
export const AssignmentLogs = () => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const [logs, setLogs] = useState([]);
|
const [logs, setLogs] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [sortBy, setSortBy] = useState('due-newest');
|
const [sortBy, setSortBy] = useState('due-newest');
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchLogs();
|
fetchLogs();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1); // Reset to page 1 when sort changes
|
||||||
|
}, [sortBy]);
|
||||||
|
|
||||||
const fetchLogs = async () => {
|
const fetchLogs = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const result = await api.getGrades(user.username);
|
const result = await api.getGrades(user.username);
|
||||||
|
|
||||||
// Transform classes into logs
|
|
||||||
const allLogs = [];
|
const allLogs = [];
|
||||||
result.user.classes.forEach(cls => {
|
result.user.classes.forEach(cls => {
|
||||||
cls.assignments.forEach(assignment => {
|
cls.assignments.forEach(assignment => {
|
||||||
@@ -63,7 +69,6 @@ export const AssignmentLogs = () => {
|
|||||||
case 'time-newest':
|
case 'time-newest':
|
||||||
sorted.sort((a, b) => {
|
sorted.sort((a, b) => {
|
||||||
const timeDiff = new Date(b.timestamp) - new Date(a.timestamp);
|
const timeDiff = new Date(b.timestamp) - new Date(a.timestamp);
|
||||||
// Tie-break with due date
|
|
||||||
if (timeDiff === 0) {
|
if (timeDiff === 0) {
|
||||||
return new Date(b.dueDate) - new Date(a.dueDate);
|
return new Date(b.dueDate) - new Date(a.dueDate);
|
||||||
}
|
}
|
||||||
@@ -74,7 +79,6 @@ export const AssignmentLogs = () => {
|
|||||||
case 'time-oldest':
|
case 'time-oldest':
|
||||||
sorted.sort((a, b) => {
|
sorted.sort((a, b) => {
|
||||||
const timeDiff = new Date(a.timestamp) - new Date(b.timestamp);
|
const timeDiff = new Date(a.timestamp) - new Date(b.timestamp);
|
||||||
// Tie-break with due date
|
|
||||||
if (timeDiff === 0) {
|
if (timeDiff === 0) {
|
||||||
return new Date(a.dueDate) - new Date(b.dueDate);
|
return new Date(a.dueDate) - new Date(b.dueDate);
|
||||||
}
|
}
|
||||||
@@ -85,7 +89,6 @@ export const AssignmentLogs = () => {
|
|||||||
case 'class-az':
|
case 'class-az':
|
||||||
sorted.sort((a, b) => {
|
sorted.sort((a, b) => {
|
||||||
const classCompare = a.className.localeCompare(b.className);
|
const classCompare = a.className.localeCompare(b.className);
|
||||||
// Within same class, sort assignments A-Z
|
|
||||||
if (classCompare === 0) {
|
if (classCompare === 0) {
|
||||||
return a.assignmentName.localeCompare(b.assignmentName);
|
return a.assignmentName.localeCompare(b.assignmentName);
|
||||||
}
|
}
|
||||||
@@ -96,7 +99,6 @@ export const AssignmentLogs = () => {
|
|||||||
case 'class-za':
|
case 'class-za':
|
||||||
sorted.sort((a, b) => {
|
sorted.sort((a, b) => {
|
||||||
const classCompare = b.className.localeCompare(a.className);
|
const classCompare = b.className.localeCompare(a.className);
|
||||||
// Within same class, sort assignments Z-A
|
|
||||||
if (classCompare === 0) {
|
if (classCompare === 0) {
|
||||||
return b.assignmentName.localeCompare(a.assignmentName);
|
return b.assignmentName.localeCompare(a.assignmentName);
|
||||||
}
|
}
|
||||||
@@ -121,7 +123,6 @@ export const AssignmentLogs = () => {
|
|||||||
|
|
||||||
const calculateGradeChanges = (sortedLogs) => {
|
const calculateGradeChanges = (sortedLogs) => {
|
||||||
return sortedLogs.map((log, index) => {
|
return sortedLogs.map((log, index) => {
|
||||||
// Look for an older version of the same assignment
|
|
||||||
const olderVersion = sortedLogs.slice(index + 1).find(
|
const olderVersion = sortedLogs.slice(index + 1).find(
|
||||||
oldLog =>
|
oldLog =>
|
||||||
oldLog.assignmentName === log.assignmentName &&
|
oldLog.assignmentName === log.assignmentName &&
|
||||||
@@ -130,7 +131,6 @@ export const AssignmentLogs = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (olderVersion) {
|
if (olderVersion) {
|
||||||
// Parse scores (remove % if present)
|
|
||||||
const newScore = parseFloat(log.newGrade.toString().replace('%', ''));
|
const newScore = parseFloat(log.newGrade.toString().replace('%', ''));
|
||||||
const oldScore = parseFloat(olderVersion.newGrade.toString().replace('%', ''));
|
const oldScore = parseFloat(olderVersion.newGrade.toString().replace('%', ''));
|
||||||
return { ...log, gradeChange: newScore - oldScore };
|
return { ...log, gradeChange: newScore - oldScore };
|
||||||
@@ -145,7 +145,6 @@ export const AssignmentLogs = () => {
|
|||||||
return date.toLocaleString('en-US', {
|
return date.toLocaleString('en-US', {
|
||||||
month: 'short',
|
month: 'short',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
year: 'numeric',
|
|
||||||
hour: 'numeric',
|
hour: 'numeric',
|
||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
hour12: true
|
hour12: true
|
||||||
@@ -153,9 +152,9 @@ export const AssignmentLogs = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getChangeIcon = (change) => {
|
const getChangeIcon = (change) => {
|
||||||
if (!change) return <Minus size={16} />;
|
if (!change) return <Minus size={14} />;
|
||||||
if (change > 0) return <TrendingUp size={16} />;
|
if (change > 0) return <TrendingUp size={14} />;
|
||||||
return <TrendingDown size={16} />;
|
return <TrendingDown size={14} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getChangeClass = (change) => {
|
const getChangeClass = (change) => {
|
||||||
@@ -185,12 +184,18 @@ export const AssignmentLogs = () => {
|
|||||||
const sortedLogs = sortLogs(logs, sortBy);
|
const sortedLogs = sortLogs(logs, sortBy);
|
||||||
const processedLogs = calculateGradeChanges(sortedLogs);
|
const processedLogs = calculateGradeChanges(sortedLogs);
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
const totalPages = Math.ceil(processedLogs.length / ITEMS_PER_PAGE);
|
||||||
|
const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
|
||||||
|
const endIndex = startIndex + ITEMS_PER_PAGE;
|
||||||
|
const currentLogs = processedLogs.slice(startIndex, endIndex);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="logs-container">
|
<div className="logs-container">
|
||||||
<div className="logs-header">
|
<div className="logs-header">
|
||||||
<div>
|
<div>
|
||||||
<h2>Assignment Logs</h2>
|
<h2>Assignment Logs</h2>
|
||||||
<p className="logs-subtitle">Track all grade updates and changes</p>
|
<p className="logs-subtitle">Tracking {processedLogs.length} assignments</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sort-controls">
|
<div className="sort-controls">
|
||||||
@@ -218,42 +223,55 @@ export const AssignmentLogs = () => {
|
|||||||
<p>No assignment logs found</p>
|
<p>No assignment logs found</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
<div className="logs-list">
|
<div className="logs-list">
|
||||||
{processedLogs.map((log, idx) => (
|
{currentLogs.map((log, idx) => (
|
||||||
<div key={idx} className="log-card">
|
<div key={idx} className="log-card">
|
||||||
<div className="log-main">
|
|
||||||
<div className="log-info">
|
|
||||||
<div className="log-class">{log.className}</div>
|
<div className="log-class">{log.className}</div>
|
||||||
<div className="log-assignment">{log.assignmentName}</div>
|
<div className="log-assignment">{log.assignmentName}</div>
|
||||||
<div className="log-meta">
|
<div className="log-meta">
|
||||||
<span className="log-category">{log.category}</span>
|
<span className="log-category">{log.category}</span>
|
||||||
<span className="log-separator">•</span>
|
|
||||||
<span className="log-period">{log.period}</span>
|
|
||||||
{log.dueDate && (
|
|
||||||
<>
|
|
||||||
<span className="log-separator">•</span>
|
|
||||||
<span className="log-due-date">Due: {log.dueDate}</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="log-due-date">{log.dueDate}</div>
|
||||||
<div className="log-grade-info">
|
|
||||||
<div className="log-grade">{log.newGrade}</div>
|
<div className="log-grade">{log.newGrade}</div>
|
||||||
|
<div className="log-change-timestamp">
|
||||||
{log.gradeChange !== 0 && (
|
{log.gradeChange !== 0 && (
|
||||||
<div className={`log-change ${getChangeClass(log.gradeChange)}`}>
|
<div className={`log-change ${getChangeClass(log.gradeChange)}`}>
|
||||||
{getChangeIcon(log.gradeChange)}
|
{getChangeIcon(log.gradeChange)}
|
||||||
<span>{Math.abs(log.gradeChange).toFixed(2)}</span>
|
<span>{Math.abs(log.gradeChange).toFixed(1)}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div className="log-timestamp">
|
||||||
|
<Clock size={12} />
|
||||||
|
{formatDate(log.timestamp)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="log-footer">
|
|
||||||
<Clock size={14} />
|
|
||||||
<span className="log-timestamp">{formatDate(log.timestamp)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="pagination">
|
||||||
|
<div className="pagination-buttons">
|
||||||
|
<button
|
||||||
|
className="pagination-btn"
|
||||||
|
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
>
|
||||||
|
<ChevronLeft size={16} />
|
||||||
|
</button>
|
||||||
|
<span className="pagination-info">
|
||||||
|
Page {currentPage} of {totalPages}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
className="pagination-btn"
|
||||||
|
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
>
|
||||||
|
<ChevronRight size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user