Supervisor Calendar Overview basic setup
Basic structure to render and show a calendar for the supervisors. Currently renders static events. When finished the supervisor will be able to look at an overview of all the 'first meetings' they have scheduled so far to facilitate easier planning of future first meeting scheduling
This commit is contained in:
parent
d2e5043c95
commit
4a4aafda51
view/src/main
@ -233,6 +233,8 @@ public class SciProApplication extends LifecycleManagedWebApplication {
|
||||
mountPage("supervisor/projectideas/studentideas", SupervisorAllStudentIdeasPage.class);
|
||||
mountPage("supervisor/projectideas/details", SupervisorIdeaDetailsPage.class);
|
||||
|
||||
mountPage("supervisor/schedule", ScheduleOverviewPage.class);
|
||||
|
||||
mountPage("supervisor/finalversions", SupervisorFinalThesisListingPage.class);
|
||||
|
||||
mountPage("supervisor/project/grading", SupervisorGradingPage.class);
|
||||
|
3
view/src/main/java/se/su/dsv/scipro/components/menuhighlighting/MenuHighLightScheduleOverview.java
Normal file
3
view/src/main/java/se/su/dsv/scipro/components/menuhighlighting/MenuHighLightScheduleOverview.java
Normal file
@ -0,0 +1,3 @@
|
||||
package se.su.dsv.scipro.components.menuhighlighting;
|
||||
|
||||
public interface MenuHighLightScheduleOverview extends MenuHighlight {}
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
|
||||
<body>
|
||||
<wicket:extend>
|
||||
<div wicket:id="myCalendarPanel"></div>
|
||||
</wicket:extend>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,21 @@
|
||||
package se.su.dsv.scipro.supervisor.pages;
|
||||
|
||||
import static se.su.dsv.scipro.security.auth.roles.Roles.SUPERVISOR;
|
||||
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
import se.su.dsv.scipro.security.auth.Authorization;
|
||||
import se.su.dsv.scipro.supervisor.panels.MyCalendarPanel;
|
||||
|
||||
@Authorization(authorizedRoles = { SUPERVISOR })
|
||||
public class ScheduleOverviewPage extends AbstractSupervisorPage {
|
||||
|
||||
public ScheduleOverviewPage() {
|
||||
super();
|
||||
add(new MyCalendarPanel("myCalendarPanel"));
|
||||
}
|
||||
|
||||
public ScheduleOverviewPage(PageParameters pp) {
|
||||
super(pp);
|
||||
add(new MyCalendarPanel("myCalendarPanel"));
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<html xmlns:wicket="http://wicket.apache.org" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<body>
|
||||
<wicket:panel>
|
||||
<div class="calendar-header-bar">
|
||||
<div class="calendar-nav-wrapper">
|
||||
<a wicket:id="prevMonth" class="calendar-nav-button">←</a>
|
||||
</div>
|
||||
|
||||
<div class="calendar-title-wrapper">
|
||||
<span wicket:id="monthLabel" class="calendar-title"></span>
|
||||
</div>
|
||||
|
||||
<div class="calendar-nav-wrapper">
|
||||
<a wicket:id="nextMonth" class="calendar-nav-button">→</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div wicket:id="calendarBody">
|
||||
<div class="calendar-grid">
|
||||
<div class="calendar-header">Mon</div>
|
||||
<div class="calendar-header">Tue</div>
|
||||
<div class="calendar-header">Wed</div>
|
||||
<div class="calendar-header">Thu</div>
|
||||
<div class="calendar-header">Fri</div>
|
||||
<div class="calendar-header">Sat</div>
|
||||
<div class="calendar-header">Sun</div>
|
||||
|
||||
<wicket:container wicket:id="rows">
|
||||
<div wicket:id="cells" class="calendar-cell">
|
||||
<div wicket:id="day-number" class="day-number"></div>
|
||||
<wicket:container wicket:id="calendar-events" />
|
||||
</div>
|
||||
</wicket:container>
|
||||
</div>
|
||||
</div>
|
||||
</wicket:panel>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,156 @@
|
||||
package se.su.dsv.scipro.supervisor.panels;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.YearMonth;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.wicket.AttributeModifier;
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.ajax.markup.html.AjaxLink;
|
||||
import org.apache.wicket.markup.head.CssHeaderItem;
|
||||
import org.apache.wicket.markup.head.IHeaderResponse;
|
||||
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.panel.Panel;
|
||||
import org.apache.wicket.markup.repeater.RepeatingView;
|
||||
import se.su.dsv.scipro.io.dto.CalendarEvent;
|
||||
|
||||
public class MyCalendarPanel extends Panel {
|
||||
|
||||
private YearMonth currentMonth;
|
||||
private WebMarkupContainer calendarBody;
|
||||
private Label monthLabel;
|
||||
|
||||
public MyCalendarPanel(String id) {
|
||||
this(id, YearMonth.now());
|
||||
}
|
||||
|
||||
public MyCalendarPanel(String id, YearMonth initialMonth) {
|
||||
super(id);
|
||||
this.currentMonth = initialMonth;
|
||||
setOutputMarkupId(true);
|
||||
|
||||
// Månadstitel
|
||||
monthLabel = new Label("monthLabel", () -> currentMonth.getMonth().name() + " " + currentMonth.getYear());
|
||||
monthLabel.setOutputMarkupId(true);
|
||||
add(monthLabel);
|
||||
|
||||
// Navigeringsknappar
|
||||
add(
|
||||
new AjaxLink<Void>("prevMonth") {
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
currentMonth = currentMonth.minusMonths(1);
|
||||
updateCalendar(target);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add(
|
||||
new AjaxLink<Void>("nextMonth") {
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
currentMonth = currentMonth.plusMonths(1);
|
||||
updateCalendar(target);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Kalenderns innehåll
|
||||
calendarBody = new WebMarkupContainer("calendarBody");
|
||||
calendarBody.setOutputMarkupId(true);
|
||||
add(calendarBody);
|
||||
|
||||
renderCalendar(calendarBody);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderHead(IHeaderResponse response) {
|
||||
super.renderHead(response);
|
||||
response.render(CssHeaderItem.forUrl("css/my_calendar.css"));
|
||||
response.render(JavaScriptHeaderItem.forUrl("js/my_calendar.js"));
|
||||
}
|
||||
|
||||
private void updateCalendar(AjaxRequestTarget target) {
|
||||
renderCalendar(calendarBody);
|
||||
target.add(calendarBody);
|
||||
target.add(monthLabel);
|
||||
}
|
||||
|
||||
private void renderCalendar(WebMarkupContainer container) {
|
||||
container.removeAll();
|
||||
|
||||
RepeatingView rows = new RepeatingView("rows");
|
||||
|
||||
LocalDate firstDayOfMonth = currentMonth.atDay(1);
|
||||
LocalDate lastDayOfMonth = currentMonth.atEndOfMonth();
|
||||
|
||||
List<LocalDate> days = new ArrayList<>();
|
||||
for (LocalDate date = firstDayOfMonth; !date.isAfter(lastDayOfMonth); date = date.plusDays(1)) {
|
||||
days.add(date);
|
||||
}
|
||||
|
||||
List<MyCalendarEvent> events = generateSampleEvents(currentMonth);
|
||||
|
||||
DayOfWeek firstDayOfWeek = DayOfWeek.MONDAY;
|
||||
int offset = (firstDayOfMonth.getDayOfWeek().getValue() - firstDayOfWeek.getValue() + 7) % 7;
|
||||
|
||||
for (int i = 0; i < offset; i++) {
|
||||
WebMarkupContainer empty = new WebMarkupContainer(rows.newChildId());
|
||||
empty.add(createEmptyCell());
|
||||
rows.add(empty);
|
||||
}
|
||||
|
||||
for (LocalDate day : days) {
|
||||
WebMarkupContainer rowItem = new WebMarkupContainer(rows.newChildId());
|
||||
WebMarkupContainer cell = new WebMarkupContainer("cells");
|
||||
cell.add(new AttributeModifier("class", "calendar-cell"));
|
||||
|
||||
Label dayLabel = new Label("day-number", String.valueOf(day.getDayOfMonth()));
|
||||
String dayClass = day.equals(LocalDate.now()) ? "day-number today" : "day-number";
|
||||
dayLabel.add(new AttributeModifier("class", dayClass));
|
||||
cell.add(dayLabel);
|
||||
|
||||
RepeatingView eventView = new RepeatingView("calendar-events");
|
||||
for (MyCalendarEvent event : events) {
|
||||
if (event.date.equals(day)) {
|
||||
Label eventLabel = new Label(eventView.newChildId(), event.description());
|
||||
eventLabel.add(new AttributeModifier("class", "calendar-event"));
|
||||
eventView.add(eventLabel);
|
||||
}
|
||||
}
|
||||
|
||||
cell.add(eventView);
|
||||
rowItem.add(cell);
|
||||
rows.add(rowItem);
|
||||
}
|
||||
|
||||
container.add(rows);
|
||||
}
|
||||
|
||||
private WebMarkupContainer createEmptyCell() {
|
||||
WebMarkupContainer cell = new WebMarkupContainer("cells");
|
||||
cell.add(new Label("day-number", ""));
|
||||
cell.add(new RepeatingView("calendar-events"));
|
||||
cell.add(new AttributeModifier("class", "calendar-cell"));
|
||||
return cell;
|
||||
}
|
||||
|
||||
private static List<MyCalendarEvent> generateSampleEvents(YearMonth yearMonth) {
|
||||
List<MyCalendarEvent> events = new ArrayList<>();
|
||||
events.add(new MyCalendarEvent("event2", yearMonth.atDay(8), "Event 2"));
|
||||
events.add(new MyCalendarEvent("event1", yearMonth.atDay(3), "Event 1"));
|
||||
events.add(new MyCalendarEvent("event3", yearMonth.atDay(8), "Event 3"));
|
||||
events.add(new MyCalendarEvent("event4", yearMonth.atDay(10), "Event 4"));
|
||||
events.add(new MyCalendarEvent("event5", yearMonth.atDay(16), "Event 5"));
|
||||
events.add(new MyCalendarEvent("event5", yearMonth.atDay(16), "Event 6"));
|
||||
events.add(new MyCalendarEvent("event5", yearMonth.atDay(16), "Event 7"));
|
||||
events.add(new MyCalendarEvent("event5", yearMonth.atDay(16), "Event 8"));
|
||||
events.add(new MyCalendarEvent("event5", yearMonth.atDay(16), "Event 9"));
|
||||
return events;
|
||||
}
|
||||
|
||||
record MyCalendarEvent(String id, LocalDate date, String description) {}
|
||||
}
|
@ -5,12 +5,14 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.wicket.Page;
|
||||
import se.su.dsv.scipro.components.AbstractMenuPanel;
|
||||
import se.su.dsv.scipro.components.menuhighlighting.MenuHighLightScheduleOverview;
|
||||
import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightSupervisorAllProjects;
|
||||
import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightSupervisorMyGroups;
|
||||
import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightSupervisorMyProjects;
|
||||
import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightSupervisorProjectIdea;
|
||||
import se.su.dsv.scipro.match.SupervisorIdeaStartPage;
|
||||
import se.su.dsv.scipro.supervisor.pages.AbstractSupervisorPage;
|
||||
import se.su.dsv.scipro.supervisor.pages.ScheduleOverviewPage;
|
||||
import se.su.dsv.scipro.supervisor.pages.SupervisorAllProjectsPage;
|
||||
import se.su.dsv.scipro.supervisor.pages.SupervisorMyGroupsPage;
|
||||
import se.su.dsv.scipro.supervisor.pages.SupervisorStartPage;
|
||||
@ -37,6 +39,7 @@ public class SupervisorTabMenuPanel extends AbstractMenuPanel {
|
||||
items.add(
|
||||
new MenuItem("All projects", SupervisorAllProjectsPage.class, MenuHighlightSupervisorAllProjects.class)
|
||||
);
|
||||
items.add(new MenuItem("Schedule Overview", ScheduleOverviewPage.class, MenuHighLightScheduleOverview.class));
|
||||
return items;
|
||||
}
|
||||
|
||||
|
143
view/src/main/webapp/css/my_calendar.css
Normal file
143
view/src/main/webapp/css/my_calendar.css
Normal file
@ -0,0 +1,143 @@
|
||||
/* Optional wrapper for full border around calendar */
|
||||
.calendar-grid-wrapper {
|
||||
border: 1px solid #ccc;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Core grid layout: 7 days */
|
||||
.calendar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
background-color: transparent; /* removes gray gaps */
|
||||
}
|
||||
|
||||
/* Headers */
|
||||
.calendar-header {
|
||||
background-color: #002F5F;
|
||||
color: #FFFFFF;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 6px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-right: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Each calendar day cell */
|
||||
.calendar-cell {
|
||||
background: white;
|
||||
border-right: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Top and left borders added only to first row/column */
|
||||
.calendar-grid > div:nth-child(7n + 1) {
|
||||
border-left: 1px solid #ccc; /* First column */
|
||||
}
|
||||
|
||||
.calendar-grid > div:nth-child(-n+7) {
|
||||
border-top: 1px solid #ccc; /* First row */
|
||||
}
|
||||
|
||||
/* Day number in the top-left */
|
||||
.day-number {
|
||||
font-weight: bold;
|
||||
font-size: 0.85em;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Today indicator a la google style */
|
||||
.day-number.today {
|
||||
background-color: #007acc;
|
||||
color: #fff;
|
||||
padding: 6px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
line-height: 16px; /* or tweak based on font-size */
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
font-size: 0.85em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Event styling */
|
||||
.calendar-event {
|
||||
display: block;
|
||||
padding: 2px 4px;
|
||||
font-size: 0.75em;
|
||||
background-color: #a5c995;
|
||||
border-left: 3px solid #007acc;
|
||||
border-radius: 2px;
|
||||
color: #1a1a1a;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Spacing between events */
|
||||
.calendar-event:first-child {
|
||||
margin-top: 1.5em; /* space below day number */
|
||||
}
|
||||
.calendar-event:not(:first-child) {
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
|
||||
/* Navigering */
|
||||
.calendar-header-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
background-color: #002F5F;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
/* Se till att navigationsknappar hamnar korrekt */
|
||||
.calendar-nav-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.calendar-nav-wrapper:first-child {
|
||||
justify-content: flex-start;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.calendar-nav-wrapper:last-child {
|
||||
justify-content: flex-end;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.calendar-nav-button {
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 12px;
|
||||
font-size: 1em;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
margin: 0 5px;
|
||||
|
||||
}
|
||||
|
||||
/* Centrera månadstiteln i mittenkolumnen */
|
||||
.calendar-title-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.calendar-title {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
28
view/src/main/webapp/js/my_calendar.js
Normal file
28
view/src/main/webapp/js/my_calendar.js
Normal file
@ -0,0 +1,28 @@
|
||||
console.log("This is my.js");
|
||||
|
||||
function normalizeCalendarHeights() {
|
||||
const cells = document.querySelectorAll('.calendar-cell');
|
||||
let maxHeight = 0;
|
||||
|
||||
if (cells.length === 0) return;
|
||||
|
||||
cells.forEach(cell => {
|
||||
cell.style.height = 'auto';
|
||||
const height = cell.scrollHeight;
|
||||
if (height > maxHeight) maxHeight = height;
|
||||
});
|
||||
|
||||
cells.forEach(cell => {
|
||||
cell.style.height = maxHeight + 'px';
|
||||
});
|
||||
}
|
||||
|
||||
// Run once when the full page loads
|
||||
window.addEventListener('load', normalizeCalendarHeights);
|
||||
|
||||
// ✨ Run after every Wicket AJAX update too — even if you add AJAX later
|
||||
if (typeof Wicket !== 'undefined') {
|
||||
Wicket.Event.subscribe('/ajax/call/complete', function() {
|
||||
normalizeCalendarHeights();
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user