151 lines
4.0 KiB
Vue
151 lines
4.0 KiB
Vue
<script setup lang="ts">
|
|
import { getDefaultEntry, type Entry } from '@/api/classroom_compliance'
|
|
import { computed, onMounted, ref, watch, type Ref } from 'vue'
|
|
|
|
const props = defineProps<{
|
|
dateStr: string
|
|
lastSaveStateTimestamp: number
|
|
}>()
|
|
|
|
const model = defineModel<Entry | null>({
|
|
required: false,
|
|
})
|
|
const initialEntryJson: Ref<string> = ref('')
|
|
const previouslyRemovedEntry: Ref<Entry | null> = ref(null)
|
|
|
|
const entryChanged = computed(() => JSON.stringify(model.value) !== initialEntryJson.value)
|
|
|
|
onMounted(() => {
|
|
initialEntryJson.value = JSON.stringify(model.value)
|
|
watch(
|
|
() => props.lastSaveStateTimestamp,
|
|
() => {
|
|
initialEntryJson.value = JSON.stringify(model.value)
|
|
previouslyRemovedEntry.value = null
|
|
},
|
|
)
|
|
})
|
|
|
|
function toggleAbsence() {
|
|
if (model.value) {
|
|
model.value.absent = !model.value.absent
|
|
if (model.value.absent) {
|
|
// Remove additional data if student is absent.
|
|
model.value.phone = null
|
|
model.value.behavior = null
|
|
} else {
|
|
// Populate default additional data if student is no longer absent.
|
|
model.value.phone = { compliant: true }
|
|
model.value.behavior = { rating: 3 }
|
|
// If we have an initial entry known, restore data from that.
|
|
if (initialEntryJson.value) {
|
|
const initialEntry = JSON.parse(initialEntryJson.value) as Entry
|
|
if (initialEntry.absent) return
|
|
if (initialEntry.phone) {
|
|
model.value.phone = { compliant: initialEntry.phone?.compliant }
|
|
}
|
|
if (initialEntry.behavior) {
|
|
model.value.behavior = { rating: initialEntry.behavior.rating }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function togglePhoneCompliance() {
|
|
if (model.value && model.value.phone) {
|
|
model.value.phone.compliant = !model.value.phone.compliant
|
|
}
|
|
}
|
|
|
|
function toggleBehaviorRating() {
|
|
if (model.value && model.value.behavior) {
|
|
model.value.behavior.rating = model.value.behavior.rating - 1
|
|
if (model.value.behavior.rating < 1) {
|
|
model.value.behavior.rating = 3
|
|
}
|
|
}
|
|
}
|
|
|
|
function removeEntry() {
|
|
if (model.value) {
|
|
previouslyRemovedEntry.value = JSON.parse(JSON.stringify(model.value))
|
|
}
|
|
model.value = null
|
|
}
|
|
|
|
function addEntry() {
|
|
if (previouslyRemovedEntry.value) {
|
|
model.value = JSON.parse(JSON.stringify(previouslyRemovedEntry.value))
|
|
} else {
|
|
model.value = getDefaultEntry(props.dateStr)
|
|
}
|
|
}
|
|
</script>
|
|
<template>
|
|
<td :class="{ absent: model?.absent, changed: entryChanged, 'missing-entry': !model }">
|
|
<div v-if="model" class="cell-container">
|
|
<div>
|
|
<div class="status-item" @click="toggleAbsence">
|
|
<span v-if="model.absent" title="Absent">❌</span>
|
|
<span v-if="!model.absent" title="Present">✅</span>
|
|
</div>
|
|
<div class="status-item" @click="togglePhoneCompliance" v-if="!model.absent">
|
|
<span v-if="model.phone?.compliant" title="Phone Compliant">📱</span>
|
|
<span v-if="!model.phone?.compliant" title="Phone Non-Compliant">📵</span>
|
|
</div>
|
|
<div class="status-item" @click="toggleBehaviorRating" v-if="!model.absent">
|
|
<span v-if="model.behavior?.rating === 3" title="Good Behavior">😇</span>
|
|
<span v-if="model.behavior?.rating === 2" title="Mediocre Behavior">😐</span>
|
|
<span v-if="model.behavior?.rating === 1" title="Poor Behavior">😡</span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="status-item" @click="removeEntry">
|
|
<span>🗑️</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="model === null">
|
|
<div class="status-item" @click="addEntry">
|
|
<span>+</span>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</template>
|
|
<style scoped>
|
|
td {
|
|
border: 1px solid black;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.missing-entry {
|
|
text-align: right;
|
|
}
|
|
|
|
.absent {
|
|
color: blue;
|
|
border: 1px solid black;
|
|
}
|
|
|
|
.changed {
|
|
border: 2px solid orange !important;
|
|
}
|
|
|
|
.status-item {
|
|
display: inline-block;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.status-item+.status-item {
|
|
margin-left: 0.5em;
|
|
}
|
|
|
|
.cell-container {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 0.25em;
|
|
}
|
|
</style>
|