Files
Website/resources/js/components/SMPanel.vue
2023-02-28 19:16:10 +10:00

398 lines
9.8 KiB
Vue

<template>
<router-link :to="to" class="sm-panel">
<div v-if="image" class="sm-panel-image" :style="styleObject">
<div v-if="dateInImage && date" class="sm-panel-image-date">
<div class="sm-panel-image-date-day">
{{ computedDay }}
</div>
<div class="sm-panel-image-date-month">
{{ computedMonth }}
</div>
</div>
<ion-icon
v-if="imageUrl.length == 0"
class="sm-panel-image-loader"
name="image-outline" />
</div>
<div class="sm-panel-body">
<h3 class="sm-panel-title">{{ title }}</h3>
<div v-if="showDate && date" class="sm-panel-date">
<ion-icon
v-if="showTime == false && endDate.length == 0"
name="calendar-outline"
class="icon" />
<ion-icon v-else name="time-outline" class="icon" />
<p>{{ computedDate }}</p>
</div>
<div v-if="location" class="sm-panel-location">
<ion-icon class="icon" name="location-outline" />
<p>{{ location }}</p>
</div>
<div v-if="price" class="sm-panel-price">
<span class="icon">$</span>
<p>{{ computedPrice }}</p>
</div>
<div v-if="content" class="sm-panel-content">
{{ computedContent }}
</div>
<div v-if="button.length > 0" class="sm-panel-button">
<SMButton
:to="to"
:type="buttonType"
:block="true"
:label="button" />
</div>
<div
v-if="banner"
:class="['sm-panel-banner', `sm-panel-banner-${bannerType}`]">
{{ banner }}
</div>
</div>
</router-link>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from "vue";
import { api } from "../helpers/api";
import { MediaResponse } from "../helpers/api.types";
import { SMDate } from "../helpers/datetime";
import { imageLoad } from "../helpers/image";
import {
excerpt,
replaceHtmlEntites,
stringToNumber,
stripHtmlTags,
} from "../helpers/string";
import { isUUID } from "../helpers/uuid";
import SMButton from "./SMButton.vue";
const props = defineProps({
title: {
type: String,
default: "",
required: true,
},
image: {
type: String,
default: "",
required: false,
},
icon: {
type: String,
default: "",
required: false,
},
to: {
type: Object,
default: () => {
return {};
},
required: true,
},
content: {
type: String,
default: "",
required: false,
},
date: {
type: String,
default: "",
required: false,
},
endDate: {
type: String,
default: "",
required: false,
},
dateInImage: {
type: Boolean,
default: true,
required: false,
},
showTime: {
type: Boolean,
default: false,
required: false,
},
showDate: {
type: Boolean,
default: true,
required: false,
},
location: {
type: String,
default: "",
required: false,
},
button: {
type: String,
default: "",
required: false,
},
buttonType: {
type: String,
default: "primary",
required: false,
},
banner: {
type: String,
default: "",
required: false,
},
bannerType: {
type: String,
default: "primary",
required: false,
},
price: {
type: String,
default: "",
required: false,
},
});
let styleObject = reactive({});
let imageUrl = ref("");
/**
* Return a human readable date based on props.date and props.endDate.
*/
const computedDate = computed(() => {
let str = "";
if (props.date.length > 0) {
if (
(props.endDate.length > 0 &&
props.date.substring(0, props.date.indexOf(" ")) !=
props.endDate.substring(0, props.endDate.indexOf(" "))) ||
props.showTime == false
) {
str = new SMDate(props.date, { format: "yMd" }).format(
"dd/MM/yyyy"
);
if (props.endDate.length > 0) {
str =
str +
" - " +
new SMDate(props.endDate, { format: "yMd" }).format(
"dd/MM/yyyy"
);
}
} else {
str = new SMDate(props.date, { format: "yMd" }).format(
"dd/MM/yyyy @ h:mm aa"
);
}
}
return str;
});
/**
* Return the content string cleaned from HTML.
*/
const computedContent = computed(() => {
return excerpt(replaceHtmlEntites(stripHtmlTags(props.content)), 200);
});
/**
* Return a computed day number from props.date
*/
const computedDay = computed(() => {
return new SMDate(props.date, { format: "yMd" }).format("dd");
});
/**
* Return a computed month name from props.date
*/
const computedMonth = computed(() => {
return new SMDate(props.date, { format: "yMd" }).format("MMM");
});
/**
* Return a computed price amount, if a form of 0, return "Free"
*/
const computedPrice = computed(() => {
const parsedPrice = stringToNumber(props.price);
if (parsedPrice == 0) {
return "Free";
}
return props.price;
});
onMounted(async () => {
if (props.image && props.image.length > 0 && isUUID(props.image)) {
api.get({ url: "/media/{medium}", params: { medium: props.image } })
.then((result) => {
const data = result.data as MediaResponse;
if (data && data.medium) {
imageLoad(data.medium.url, (url) => {
imageUrl.value = url;
});
}
})
.catch(() => {
/* empty */
});
}
});
watch(
() => imageUrl.value,
(value) => {
styleObject["backgroundImage"] = `url('${value}')`;
}
);
</script>
<style lang="scss">
.sm-panel {
display: flex;
flex-direction: column;
border: 1px solid $border-color;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 0 28px rgba(0, 0, 0, 0.05);
max-width: 21rem;
width: 100%;
color: $font-color !important;
margin-bottom: map-get($spacer, 5);
transition: box-shadow 0.2s ease-in-out;
position: relative;
overflow: hidden;
&:hover {
color: $font-color;
text-decoration: none;
box-shadow: 0 0 14px rgba(0, 0, 0, 0.25);
}
.sm-panel-image {
position: relative;
display: flex;
justify-content: center;
align-items: center;
height: map-get($spacer, 5) * 4;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
background-color: #eee;
.sm-panel-image-loader {
font-size: 5rem;
color: $secondary-color;
}
.sm-panel-image-date {
background-color: #fff;
padding: 0.75rem 1rem;
text-align: center;
position: absolute;
top: 15px;
left: 15px;
border-radius: 4px;
box-shadow: 4px 4px 15px rgba(0, 0, 0, 0.2);
text-align: center;
.sm-panel-image-date-day {
font-weight: bold;
font-size: 130%;
}
.sm-panel-image-date-month {
text-transform: uppercase;
font-size: 80%;
}
}
}
.sm-panel-body {
display: flex;
flex-direction: column;
flex: 1;
padding: 0 map-get($spacer, 3) map-get($spacer, 3) map-get($spacer, 3);
background-color: #fff;
}
.sm-panel-title {
margin-bottom: 1rem;
}
.sm-panel-date,
.sm-panel-location,
.sm-panel-price {
display: flex;
flex-direction: row;
align-items: top;
font-size: 80%;
margin-bottom: 0.4rem;
.icon {
flex: 0 1 1rem;
margin-right: map-get($spacer, 1);
padding-top: 0.1rem;
height: 1rem;
padding: 0.25rem 0;
text-align: center;
}
p {
flex: 1;
margin: 0;
line-height: 1.5rem;
}
}
.sm-panel-content {
margin-top: 1rem;
line-height: 130%;
flex: 1;
}
.sm-panel-button {
margin-top: map-get($spacer, 4);
}
.sm-panel-banner {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
top: 65px;
right: -10px;
height: 20px;
width: 120px;
font-size: 70%;
text-transform: uppercase;
font-weight: 800;
color: #fff;
background-color: $primary-color;
transform-origin: 100%;
transform: rotateZ(45deg);
&.sm-panel-banner-success {
background-color: $success-color;
}
&.sm-panel-banner-danger {
background-color: $danger-color;
font-size: 60%;
}
&.sm-panel-banner-warning {
background-color: $warning-color-darker;
color: $font-color;
font-size: 60%;
}
&.sm-panel-banner-expired {
background-color: purple;
}
}
}
</style>