teacher-tools/app/src/apps/classroom_compliance/EntryTableCell.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>