Commit 85708081 by Samuel Taniel Mulyadi

Merge branch 'staging' into 'master'

Staging

See merge request !12
2 parents ffdda07b 3325030e
Showing with 198 additions and 131 deletions
...@@ -131,18 +131,18 @@ function getColorClass(index: number) { ...@@ -131,18 +131,18 @@ function getColorClass(index: number) {
<template> <template>
<VCard class="timetable-card"> <VCard class="timetable-card">
<template #title> <template #title>
<div class="d-flex align-center"> <div class="d-flex align-center header-container">
<!-- Left: Title --> <!-- Title -->
<span class="me-auto">Jadwal Kuliah</span> <span class="me-auto">Jadwal Kuliah</span>
<!-- Right: Date Range Box --> <!-- Date Range Box (desktop version) -->
<div class="me-auto date-range-box"> <div class="me-auto date-range-box">
<span v-if="schedule.length > 0"> <span v-if="schedule.length > 0">
{{ schedule[0].tgl_mulai }} / {{ schedule[0].tgl_selesai }} {{ schedule[0].tgl_mulai }} / {{ schedule[0].tgl_selesai }}
</span> </span>
</div> </div>
<!-- Right: Icons --> <!-- Icons -->
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<VBtn <VBtn
icon icon
...@@ -160,8 +160,26 @@ function getColorClass(index: number) { ...@@ -160,8 +160,26 @@ function getColorClass(index: number) {
> >
<VIcon icon="ri-layout-horizontal-line" /> <VIcon icon="ri-layout-horizontal-line" />
</VBtn> </VBtn>
<!--
<VBtn
icon
variant="text"
color="black"
title="Download horizontal schedule as image"
@click="downloadHorizontalSchedule"
>
<VIcon icon="ri-download-2-line" />
</VBtn>
-->
</div> </div>
</div> </div>
<!-- Date Range Box (mobile version) -->
<div class="date-range-box2">
<span v-if="schedule.length > 0">
{{ schedule[0].tgl_mulai }} / {{ schedule[0].tgl_selesai }}
</span>
</div>
</template> </template>
<VCard <VCard
...@@ -169,68 +187,70 @@ function getColorClass(index: number) { ...@@ -169,68 +187,70 @@ function getColorClass(index: number) {
class="timetable" class="timetable"
> >
<!-- Header Row --> <!-- Header Row -->
<div class="grid-header"> <div class="scrollable">
<div class="time-label"> <div class="grid-header">
Jam <div class="time-label">
</div> Jam
<div </div>
v-for="day in daysOfWeek"
:key="day"
class="day-header"
>
{{ day }}
</div>
</div>
<!-- Grid Body -->
<div class="grid-body">
<!-- Time Labels -->
<div class="time-column">
<div <div
v-for="hour in Array.from({ length: endHour - startHour + 1 }, (_, i) => startHour + i)" v-for="day in daysOfWeek"
:key="hour" :key="day"
class="time-slot" class="day-header"
:style="{
gridRowStart: (hour - startHour) * 60 + 1, // Now each row is a single minute
gridRowEnd: `span 60`, // Each hour label spans 60 rows
gridColumn: 1, // Stays in the first column
}"
> >
{{ hour }}:00 {{ day }}
</div> </div>
</div> </div>
<!-- Schedule Grid --> <!-- Grid Body -->
<div class="schedule-grid"> <div class="grid-body">
<div <!-- Time Labels -->
v-for="item in schedule" <div class="time-column">
:key="item.course + item.start" <div
class="schedule-item" v-for="hour in Array.from({ length: endHour - startHour + 1 }, (_, i) => startHour + i)"
:style="{ :key="hour"
gridRowStart: parseTime(item.start) + 1, // Ensure it starts correctly class="time-slot"
gridRowEnd: `span ${calculateRowSpan(item.start, item.end)}`, :style="{
gridColumn: daysOfWeek.indexOf(item.day) + 1, //its already right because monday starts at 0 so time column +1 gridRowStart: (hour - startHour) * 60 + 1, // Now each row is a single minute
}" gridRowEnd: `span 60`, // Each hour label spans 60 rows
> gridColumn: 1, // Stays in the first column
<div class="course-header"> }"
<span class="time"> >
<i class="ri-time-line" /> {{ item.start }} - {{ item.end }} {{ hour }}:00
</span>
<span class="room">{{ item.room }}</span>
</div>
<div class="course-title">
<span
v-if="item.course.includes('-')"
class="course-prefix"
>
{{ item.course.split(' - ')[0] }}
</span>
<span class="course-name">
{{ item.course.includes('-') ? item.course.split(' - ')[1] : item.course }}
</span>
</div> </div>
<div class="building"> </div>
{{ item.building }}
<!-- Schedule Grid -->
<div class="schedule-grid">
<div
v-for="item in schedule"
:key="item.course + item.start"
class="schedule-item"
:style="{
gridRowStart: parseTime(item.start) + 1, // Ensure it starts correctly
gridRowEnd: `span ${calculateRowSpan(item.start, item.end)}`,
gridColumn: daysOfWeek.indexOf(item.day) + 1, //its already right because monday starts at 0 so time column +1
}"
>
<div class="course-header">
<span class="time">
<i class="ri-time-line" /> {{ item.start }} - {{ item.end }}
</span>
<span class="room">{{ item.room }}</span>
</div>
<div class="course-title">
<span
v-if="item.course.includes('-')"
class="course-prefix"
>
{{ item.course.split(' - ')[0] }}
</span>
<span class="course-name">
{{ item.course.includes('-') ? item.course.split(' - ')[1] : item.course }}
</span>
</div>
<div class="building">
{{ item.building }}
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -238,84 +258,121 @@ function getColorClass(index: number) { ...@@ -238,84 +258,121 @@ function getColorClass(index: number) {
</VCard> </VCard>
<VCard <VCard
v-else v-else
class="timetable px-4 py-6" class="timetable"
> >
<table class="w-100 text-left table-schedule fixed-table"> <div class="scrollable">
<thead> <table class="w-100 text-left table-schedule fixed-table">
<tr> <thead>
<th <tr>
v-for="day in daysOfWeek" <th
:key="day" v-for="day in daysOfWeek"
class="px-2 py-2" :key="day"
> class="px-2 py-2"
{{ day }} >
</th> {{ day }}
</tr> </th>
</thead> </tr>
<tbody> </thead>
<tr <tbody>
v-for="rowIndex in 10" <tr
:key="`row-${rowIndex}`" v-for="rowIndex in 10"
> :key="`row-${rowIndex}`"
<td
v-for="(day, dayIndex) in daysOfWeek"
:key="day + rowIndex"
class="align-top px-2 py-3"
> >
<div <td
v-if="getScheduleByDay(day)[rowIndex - 1]" v-for="(day, dayIndex) in daysOfWeek"
class="schedule-box" :key="day + rowIndex"
:class="[getColorClass(rowIndex - 1)]" class="align-top px-2 py-3"
> >
<div class="course-header mb-1"> <div
<span class="text-sm font-medium d-flex align-center gap-1"> v-if="getScheduleByDay(day)[rowIndex - 1]"
<i class="ri-time-line" /> {{ class="schedule-box"
getScheduleByDay(day)[rowIndex - 1].start :class="[getColorClass(rowIndex - 1)]"
}} - {{ >
getScheduleByDay(day)[rowIndex - 1].end <div class="course-header mb-1">
}} <span class="time text-sm">
</span> <i class="ri-time-line" /> {{
<span class="text-xs">{{ getScheduleByDay(day)[rowIndex - 1].room }}</span> getScheduleByDay(day)[rowIndex - 1].start
</div> }} - {{
<div class="course-title font-semibold text-sm mb-1"> getScheduleByDay(day)[rowIndex - 1].end
<span }}
v-if="getScheduleByDay(day)[rowIndex - 1].course.includes('-')" </span>
style="text-decoration: underline;" <span class="room text-xs">{{ getScheduleByDay(day)[rowIndex - 1].room }}</span>
> </div>
{{ getScheduleByDay(day)[rowIndex - 1].course.split(' - ')[0] }} <div class="course-title font-semibold text-sm mb-1">
</span> <span
<span class="text-sm"> v-if="getScheduleByDay(day)[rowIndex - 1].course.includes('-')"
{{ style="text-decoration: underline;"
getScheduleByDay(day)[rowIndex - 1].course.includes('-') >
? getScheduleByDay(day)[rowIndex - 1].course.split(' - ')[1] {{ getScheduleByDay(day)[rowIndex - 1].course.split(' - ')[0] }}
: getScheduleByDay(day)[rowIndex - 1].course </span>
}} <span class="text-sm">
</span> {{
</div> getScheduleByDay(day)[rowIndex - 1].course.includes('-')
<div class="text-xs"> ? getScheduleByDay(day)[rowIndex - 1].course.split(' - ')[1]
{{ getScheduleByDay(day)[rowIndex - 1].building }} : getScheduleByDay(day)[rowIndex - 1].course
}}
</span>
</div>
<div class="building text-xs">
{{ getScheduleByDay(day)[rowIndex - 1].building }}
</div>
</div> </div>
</div> </td>
</td> </tr>
</tr> </tbody>
</tbody> </table>
</table> </div>
</VCard> </VCard>
</VCard> </VCard>
</template> </template>
<style scoped> <style scoped>
.date-range-box { .scrollable {
display: inline-block; overflow: auto hidden; /* Optional: you can allow vertical scroll if needed */
box-sizing: border-box;
inline-size: 100%;
min-inline-size: 500px;
}
/* Optional: make inner content grow past 700px if needed */
.scrollable > * {
min-inline-size: 500px;
}
.date-range-box,
.date-range-box2 {
border-radius: 5px; border-radius: 5px;
background-color: rgba(var(--v-global-theme-primary), 0.1); background-color: rgba(var(--v-global-theme-primary), 0.1);
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
margin-inline-start: 10px;
padding-block: 5px; padding-block: 5px;
padding-inline: 10px; padding-inline: 10px;
} }
/* Default: show inline box, hide full-width box */
.date-range-box {
display: inline-block;
margin-inline-start: 10px;
}
.date-range-box2 {
display: none;
}
/* Wrapped layout or small screens */
@media (max-width: 600px) {
.date-range-box {
display: none;
}
.date-range-box2 {
display: flex;
justify-content: center;
inline-size: 100%;
margin-block-start: 8px;
}
}
.timetable { .timetable {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -345,15 +402,17 @@ function getColorClass(index: number) { ...@@ -345,15 +402,17 @@ function getColorClass(index: number) {
} }
.course-title { .course-title {
display: flex; display: -webkit-box;
flex-wrap: wrap; overflow: hidden;
border-block-end: 1px solid; border-block-end: 1px solid;
-webkit-box-orient: vertical;
font-size: 12px; font-size: 12px;
font-weight: bold; font-weight: bold;
gap: 4px; inline-size: 100%;
inline-size: 100%; /* Ensures the border spans the entire container */ -webkit-line-clamp: 2; /* Number of lines before cutting */
margin-block-end: 5px; margin-block-end: 5px;
padding-block-end: 5px; padding-block-end: 5px;
text-overflow: ellipsis;
} }
.course-prefix { .course-prefix {
...@@ -414,22 +473,26 @@ function getColorClass(index: number) { ...@@ -414,22 +473,26 @@ function getColorClass(index: number) {
.course-header { .course-header {
display: flex; display: flex;
justify-content: space-between; /* Pushes elements to the left & right */ justify-content: space-between;
inline-size: 100%; /* Ensures full width */ gap: 8px; /* optional spacing */
text-overflow: ellipsis; /* Adds "..." when text is too long */ inline-size: 100%;
word-break: break-word; /* Allows text to wrap naturally */
} }
.time { .time {
flex-shrink: 0; /* Don't shrink */
font-weight: normal; font-weight: normal;
text-align: start; /* Aligns time to the left */ text-align: start;
white-space: nowrap;
} }
.room { .room {
overflow: hidden; overflow: hidden;
flex-grow: 1;
flex-shrink: 1;
font-weight: normal; font-weight: normal;
text-align: end; /* Aligns room to the right */ text-align: end;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap;
} }
.building { .building {
...@@ -493,6 +556,10 @@ function getColorClass(index: number) { ...@@ -493,6 +556,10 @@ function getColorClass(index: number) {
.time { .time {
display: none; display: none;
} }
.room {
text-align: start;
}
} }
@media screen and (max-width: 900px) { @media screen and (max-width: 900px) {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!