Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 14 additions & 25 deletions assets/vue/components/course/CourseCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
</div>

<div
v-if="sessionDisplayDate"
v-if="showSessionDisplayDate && sessionDisplayDate"
class="session__display-date"
v-text="sessionDisplayDate"
/>
Expand Down Expand Up @@ -117,6 +117,11 @@ const props = defineProps({
required: false,
default: false,
},
showSessionDisplayDate: {
type: Boolean,
required: false,
default: true,
},
})

const { t } = useI18n()
Expand All @@ -128,12 +133,12 @@ const showRemainingDays = computed(
)

const daysRemainingText = computed(() => {
if (!showRemainingDays.value || !props.session?.displayEndDate) {
if (!showRemainingDays.value || isCoach.value || !props.session?.displayEndDate) {
return null
}

const endDate = new Date(props.session.displayEndDate)
if (isNaN(endDate)) {
if (isNaN(endDate.getTime())) {
return null
}

Expand Down Expand Up @@ -166,19 +171,15 @@ const sessionDurationText = computed(() => {
const start = new Date(props.session.displayStartDate)
const end = new Date(props.session.displayEndDate)

if (isNaN(start) || isNaN(end)) {
if (isNaN(start.getTime()) || isNaN(end.getTime())) {
return null
}

const msPerDay = 1000 * 60 * 60 * 24
const rawDiff = Math.floor((end - start) / msPerDay) + 1
const days = rawDiff > 0 ? rawDiff : 1

if (days === 1) {
return "1 day duration"
}

return `${days} days duration`
return days === 1 ? "1 day duration" : `${days} days duration`
})

const showCourseDuration = computed(() => platformConfigStore.getSetting("course.show_course_duration") === "true")
Expand All @@ -201,24 +202,12 @@ const teachers = computed(() => {
})

const sessionDisplayDate = computed(() => {
// When setting is enabled, decide between duration (for coaches) and remaining days (for regular users)
if (sessionDurationText.value) {
return sessionDurationText.value
}
if (sessionDurationText.value) return sessionDurationText.value
if (daysRemainingText.value) return daysRemainingText.value

if (daysRemainingText.value) {
return daysRemainingText.value
}

// Fallback: show the original date range
const parts = []
if (props.session?.displayStartDate) {
parts.push(abbreviatedDatetime(props.session.displayStartDate))
}
if (props.session?.displayEndDate) {
parts.push(abbreviatedDatetime(props.session.displayEndDate))
}

if (props.session?.displayStartDate) parts.push(abbreviatedDatetime(props.session.displayStartDate))
if (props.session?.displayEndDate) parts.push(abbreviatedDatetime(props.session.displayEndDate))
return parts.join(" — ")
})

Expand Down
1 change: 1 addition & 0 deletions assets/vue/components/session/SessionCardSimple.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ function normalizeCourse(course) {
:disabled="!isEnabled"
:session="session"
:session-id="session.id"
:show-session-display-date="false"
/>
</div>
</div>
Expand Down
128 changes: 124 additions & 4 deletions assets/vue/components/session/SessionListView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
{{ session.name || session.title || "Untitled Session" }}
</div>
<div class="text-sm text-gray-50 mt-1">
{{ session.displayStartDate ? formatDate(session.displayStartDate) : "" }}
<span v-if="session.displayStartDate && session.displayEndDate"> - </span>
{{ session.displayEndDate ? formatDate(session.displayEndDate) : "" }}
{{ getSessionDisplayLabel(session) }}
</div>
</div>
<div class="flex items-center gap-4">
Expand Down Expand Up @@ -59,20 +57,26 @@
</div>
</template>
<script setup>
import { computed, ref } from "vue"
import { useI18n } from "vue-i18n"
import { useSecurityStore } from "../../store/securityStore"
import { ref } from "vue"
import { usePlatformConfig } from "../../store/platformConfig"
import SessionCardSimple from "./SessionCardSimple.vue"

const { t } = useI18n()
const securityStore = useSecurityStore()
const platformConfigStore = usePlatformConfig()

const props = defineProps({
uncategorizedSessions: Array,
categories: Array,
categoriesWithSessions: Map,
})

const showRemainingDays = computed(
() => platformConfigStore.getSetting("session.session_list_view_remaining_days") === "true",
)

function formatDate(iso) {
const date = new Date(iso)
return date.toLocaleDateString("en-US", {
Expand All @@ -82,6 +86,122 @@ function formatDate(iso) {
})
}

function getCurrentUserId() {
// Try multiple shapes to be resilient across environments.
return (
securityStore.user?.id ||
securityStore.user?._id ||
securityStore.userId ||
securityStore.currentUser?.id ||
null
)
}

function extractIdFromIri(iri) {
if (!iri) return null
const match = iri.match(/\/(\d+)$/)
return match ? parseInt(match[1], 10) : null
}

function isCoachForSession(session) {
// Admins generally have permanent access too.
if (securityStore.isAdmin) return true

const uid = getCurrentUserId()
if (!uid) return false

// Session creator/owner often has permanent access in practice.
const creatorId =
session.creator?.id ||
session.creatorId ||
session.createdBy?.id ||
session.owner?.id ||
session.user?.id ||
null

if (creatorId && Number(creatorId) === Number(uid)) return true

// General coach lists (try common fields)
const sessionCoachLists = [
session.sessionCoaches,
session.coaches,
session.generalCoaches,
session.coachSubscriptions,
session.sessionCoachSubscriptions,
].filter(Boolean)

for (const list of sessionCoachLists) {
if (Array.isArray(list)) {
const found = list.some((item) => {
const id = item?.id || item?._id || item?.user?.id || item?.user?._id || extractIdFromIri(item?.["@id"])
return id && Number(id) === Number(uid)
})
if (found) return true
}
}

// Course coach subscriptions exist in your DTO (you already use it in CourseCard).
if (Array.isArray(session.courseCoachesSubscriptions)) {
const found = session.courseCoachesSubscriptions.some((sub) => {
const user = sub?.user
const id = user?.id || user?._id || extractIdFromIri(user?.["@id"])
return id && Number(id) === Number(uid)
})
if (found) return true
}

return false
}

function getRemainingText(session) {
if (!session?.displayEndDate) return null

const endDate = new Date(session.displayEndDate)
if (isNaN(endDate.getTime())) return null

const today = new Date()
const diff = Math.floor((endDate - today) / (1000 * 60 * 60 * 24))

if (diff > 1) return `${diff} days remaining`
if (diff === 1) return t("Ends tomorrow")
if (diff === 0) return t("Ends today")
return t("Expired")
}

function getDurationText(session) {
if (!session?.displayStartDate || !session?.displayEndDate) return null

const start = new Date(session.displayStartDate)
const end = new Date(session.displayEndDate)

if (isNaN(start.getTime()) || isNaN(end.getTime())) return null

const msPerDay = 1000 * 60 * 60 * 24
const rawDiff = Math.floor((end - start) / msPerDay) + 1
const days = rawDiff > 0 ? rawDiff : 1

return days === 1 ? "1 day duration" : `${days} days duration`
}

function getSessionDisplayLabel(session) {
// Setting OFF: keep old behavior.
if (!showRemainingDays.value) {
const left = session.displayStartDate ? formatDate(session.displayStartDate) : ""
const right = session.displayEndDate ? formatDate(session.displayEndDate) : ""
if (left && right) return `${left} - ${right}`
return left || right || ""
}

// Setting ON:
// - Coaches/creator/admin: show duration
// - Students: show remaining/expired
if (isCoachForSession(session)) {
return getDurationText(session) || ""
}

return getRemainingText(session) || ""
}

function goToEdit(sessionId) {
window.location.href = `/main/session/resume_session.php?id_session=${sessionId}`
}
Expand Down
12 changes: 11 additions & 1 deletion public/main/session/session_add.php
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,17 @@ function (User $user) {
$displayEndDate = $params['display_end_date'];
$coachStartDate = $params['coach_access_start_date'] ?? $displayStartDate;
$coachEndDate = $params['coach_access_end_date'];
$coachUsername = $params['coach_username'];
$coachUsername = $params['coach_username'] ?? [];

if (empty($coachUsername)) {
// Keep the historical default behavior: current user becomes the coach
$coachUsername = [api_get_user_id()];
}

if (!is_array($coachUsername)) {
$coachUsername = [$coachUsername];
}

$id_session_category = (int) $params['session_category'];
$id_visibility = $params['session_visibility'];
$duration = $params['duration'] ?? null;
Expand Down
15 changes: 15 additions & 0 deletions src/CoreBundle/Entity/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,9 @@ class Session implements ResourceWithAccessUrlInterface, Stringable
#[ORM\Column(name: 'validity_in_days', type: 'integer', nullable: true)]
protected ?int $validityInDays = null;

#[Groups(['user_subscriptions:sessions'])]
private ?int $daysLeft = null;

public function __construct()
{
$this->skills = new ArrayCollection();
Expand Down Expand Up @@ -1676,4 +1679,16 @@ public function setValidityInDays(?int $validityInDays): self

return $this;
}

public function getDaysLeft(): ?int
{
return $this->daysLeft;
}

public function setDaysLeft(?int $daysLeft): self
{
$this->daysLeft = $daysLeft;

return $this;
}
}
2 changes: 0 additions & 2 deletions src/CoreBundle/Repository/SessionRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ public function getPastSessionsOfUserInUrl(User $user, AccessUrl $url): array
// Check if the session has a duration
if ($session->getDuration() > 0) {
$daysLeft = $session->getDaysLeftByUser($user);
$session->setTitle($session->getTitle().'<-'.$daysLeft);

return $daysLeft < 0 && !$userIsCoach;
}

Expand Down
Loading
Loading