Layout
unknown
html
2 years ago
34 kB
16
Indexable
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div id="layout" v-cloak>
<div class="flex items-center justify-center h-screen" v-if="isPageLoading">
<div class="loading-spinner">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<div v-else>
<!-- Off-canvas menu for mobile, show/hide based on off-canvas menu state. -->
<div v-if="isSideNavOpen" class="relative z-50 lg:hidden" role="dialog" aria-modal="true">
<!--
Off-canvas menu backdrop, show/hide based on off-canvas menu state.
Entering: "transition-opacity ease-linear duration-300"
From: "opacity-0"
To: "opacity-100"
Leaving: "transition-opacity ease-linear duration-300"
From: "opacity-100"
To: "opacity-0"
-->
<div class="fixed inset-0 bg-gray-900/80"></div>
<div class="fixed inset-0 flex">
<!--
Off-canvas menu, show/hide based on off-canvas menu state.
Entering: "transition ease-in-out duration-300 transform"
From: "-translate-x-full"
To: "translate-x-0"
Leaving: "transition ease-in-out duration-300 transform"
From: "translate-x-0"
To: "-translate-x-full"
-->
<div class="relative mr-16 flex w-full max-w-xs flex-1">
<!--
Close button, show/hide based on off-canvas menu state.
Entering: "ease-in-out duration-300"
From: "opacity-0"
To: "opacity-100"
Leaving: "ease-in-out duration-300"
From: "opacity-100"
To: "opacity-0"
-->
<div class="absolute left-full top-0 flex w-16 justify-center pt-5">
<button @click="openSideNavOpen" type="button" class="-m-2.5 p-2.5">
<span class="sr-only">Close sidebar</span>
<svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<!-- Sidebar component, swap this element with another sidebar if you like -->
<div class="flex grow flex-col gap-y-5 overflow-y-auto bg-white px-6 pb-4">
<div class="flex h-16 shrink-0 items-center mt-5">
<img class="mx-auto h-10 w-auto" src="{% static 'logo.png' %}">
</div>
<nav class="flex flex-1 flex-col">
<ul role="list" class="flex flex-1 flex-col gap-y-7">
<li>
<ul role="list" class="-mx-2 space-y-1">
<li>
<!-- Current: "bg-gray-50 text-indigo-600", Default: "text-gray-700 hover:text-indigo-600 hover:bg-gray-50" -->
<a href="/"
class="bg-gray-50 text-indigo-600 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<svg class="h-6 w-6 shrink-0 text-indigo-600" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"/>
</svg>
Homepage
</a>
</li>
<li>
<a href="/chats/room"
class="text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<svg class="h-6 w-6 shrink-0 text-gray-400 group-hover:text-indigo-600"
fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"/>
</svg>
Chat Room
</a>
</li>
<li>
<a href="/tasks"
class="text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<svg class="h-6 w-6 shrink-0 text-gray-400 group-hover:text-indigo-600"
fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z"/>
</svg>
Tasks
</a>
</li>
</ul>
</li>
</ul>
</nav>
</div>
</div>
</div>
</div>
<!-- Static sidebar for desktop -->
<div class="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col">
<!-- Sidebar component, swap this element with another sidebar if you like -->
<div class="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-6 pb-4">
<div class="flex h-16 shrink-0 items-center mt-20">
<img class="mx-auto h-24 w-auto" src="{% static 'logo.png' %}">
</div>
<nav class="mt-10 flex flex-1 flex-col">
<ul role="list" class="flex flex-1 flex-col gap-y-7">
<li>
<ul role="list" class="-mx-2 space-y-1">
<li>
<!-- Current: "bg-gray-50 text-indigo-600", Default: "text-gray-700 hover:text-indigo-600 hover:bg-gray-50" -->
<a href="/"
class="bg-gray-50 text-indigo-600 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<svg class="h-6 w-6 shrink-0 text-indigo-600" fill="none"
viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"/>
</svg>
Homepage
</a>
</li>
<li>
<a href="/chats/room"
class="text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<svg class="h-6 w-6 shrink-0 text-gray-400 group-hover:text-indigo-600"
fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor"
aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"/>
</svg>
Chat Room
</a>
</li>
<li>
<a href="/tasks"
class="text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<svg class="h-6 w-6 shrink-0 text-gray-400 group-hover:text-indigo-600"
fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor"
aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z"/>
</svg>
Tasks
</a>
</li>
</ul>
</li>
</ul>
</nav>
</div>
</div>
<div class="lg:pl-72">
<div class="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-x-4 border-b border-gray-200 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:px-8">
<button @click="openSideNavOpen" type="button" class="-m-2.5 p-2.5 text-gray-700 lg:hidden">
<span class="sr-only">Open sidebar</span>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/>
</svg>
</button>
<!-- Separator -->
<div class="h-6 w-px bg-gray-200 lg:hidden" aria-hidden="true"></div>
<div class="flex flex-1 gap-x-4 self-stretch lg:gap-x-6">
<div class="relative flex flex-1" >
</div>
<div class="flex items-center gap-x-4 lg:gap-x-6">
<!-- Separator -->
<div class="hidden lg:block lg:h-6 lg:w-px lg:bg-gray-200" aria-hidden="true"></div>
<!-- Profile dropdown -->
<div class="relative">
<button @click="openSidebar" type="button" class="-m-1.5 flex items-center p-1.5"
id="user-menu-button"
aria-expanded="false" aria-haspopup="true">
<span class="sr-only">Open user menu</span>
<img class="h-8 w-8 rounded-full bg-gray-50"
src="{{ user.profile_picture.url }}"
alt="">
<span class="hidden lg:flex lg:items-center">
<span class="ml-4 text-sm font-semibold leading-6 text-white"
aria-hidden="true">{{ user.first_name }} {{ user.last_name }}</span>
<svg class="ml-2 h-5 w-5 text-white" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
clip-rule="evenodd"/>
</svg>
</span>
</button>
<div v-if="isOpen"
class="absolute right-0 z-10 mt-2.5 w-32 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-gray-900/5 focus:outline-none"
role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button"
tabindex="-1">
<!-- Active: "bg-gray-50", Not Active: "" -->
<a href="/profile" class="block px-3 py-1 text-sm leading-6 text-gray-900"
role="menuitem"
tabindex="-1" id="user-menu-item-1">Profile</a>
<a href="/accounts/logout/" class="block px-3 py-1 text-sm leading-6 text-gray-900"
role="menuitem"
tabindex="-1" id="user-menu-item-1">Sign out</a>
</div>
</div>
</div>
</div>
</div>
<main class="py-10">
<div class="mx-auto max-w-9xl px-4 sm:px-10 lg:px-8">
{% block layout %}
{% endblock %}
</div>
</main>
</div>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="module">
const {createApp} = Vue;
const delimiters = ['[[', ']]']
const chat_room = "{{ pk }}"
const user_id = "{{ user.id }}"
createApp({
delimiters,
data() {
return {
isOpen: false,
isSideNavOpen: false,
todoList: [],
inProgressList: [],
doneList: [],
showTasks: true,
showAIModal: false,
isLoading: false,
isChatFormModalOpen: false,
isPageLoading: false,
isDeleteModalOpen: false,
prompt: "",
title: "",
assignee: "",
deadline: "",
description: "",
chat_name: "",
message: "",
chat_room_id: "",
chat_members: [],
chat_rooms: [],
chat_messages: [],
currentDate: new Date(),
user_id
}
},
methods: {
openSidebar() {
this.isOpen = !this.isOpen
},
openSideNavOpen() {
this.isSideNavOpen = !this.isSideNavOpen
},
openTasksForm() {
this.showTasks = !this.showTasks
},
openAIModal() {
this.showAIModal = !this.showAIModal
},
openChatFormModalOpen() {
this.isChatFormModalOpen = !this.isChatFormModalOpen
},
deleteChatRoom(id) {
this.isDeleteModalOpen = !this.isDeleteModalOpen
this.chat_room_id = id
},
async getTasksToDo() {
try {
const resp = await apiGetTasksToDo()
this.todoList = resp
} catch (err) {
console.log(err)
}
},
async getTasksInProgress() {
try {
const resp = await apiGetTasksInProgress()
this.inProgressList = resp
} catch (err) {
console.log(err)
}
},
async getTasksDone() {
try {
const resp = await apiGetTasksDone()
this.doneList = resp
} catch (err) {
console.log(err)
}
},
async moveTasks(id, status) {
const payload = {
id: id,
status: status
}
try {
const resp = await apiMoveTasks(payload)
await this.getTasksToDo()
await this.getTasksInProgress()
await this.getTasksDone()
} catch (err) {
console.log(err)
}
},
async aIGenerate() {
const payload = {
prompt: this.prompt
}
try {
this.isLoading = !this.isLoading
const resp = await apiAIGenerate(payload)
this.isLoading = !this.isLoading
this.description = resp
if (resp) {
this.isLoading = !this.isLoading
this.showAIModal = !this.showAIModal
}
} catch (err) {
console.log(err)
}
},
async createTasks() {
const payload = {
title: this.title,
assignee: this.assignee,
deadline: this.deadline,
description: this.description
}
try {
const resp = await apiAddTasks(payload)
if (resp) {
this.showTasks = !this.showTasks
}
} catch (err) {
console.log(err)
}
},
async createNewChatRoom() {
const payload = {
name: this.chat_name,
members: this.chat_members
}
try {
const resp = await apiCreateNewChatRoom(payload)
if (resp) {
this.isChatFormModalOpen = !this.isChatFormModalOpen
await this.getChatRoomsLists()
}
} catch (err) {
console.log(err)
}
},
async getChatRoomsLists() {
try {
const resp = await apiChatRoomList()
this.chat_rooms = resp
} catch (err) {
console.log(err)
}
},
formatRelativeTime(dateString) {
const now = new Date();
const date = new Date(dateString);
const diffInMilliseconds = now - date;
const diffInSeconds = Math.floor(diffInMilliseconds / 1000);
if (diffInSeconds < 1) {
return 'just now';
} else if (diffInSeconds < 60) {
return `last ${diffInSeconds} seconds ago`;
} else {
const diffInMinutes = Math.floor(diffInSeconds / 60);
const diffInHours = Math.floor(diffInMinutes / 60);
if (diffInMinutes < 1) {
return 'just now';
} else if (diffInHours < 1) {
return `last ${diffInMinutes} minutes ago`;
} else if (diffInHours < 24) {
return `last ${diffInHours} hours ago`;
} else {
const options = {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
};
return new Intl.DateTimeFormat('en-US', options).format(date);
}
}
},
async getChatMessages() {
window.setInterval(async () => {
try {
const resp = await apiGetChatMessages()
this.chat_messages = resp
} catch (err) {
console.log(err)
}
}, 1000)
},
async sendChatMessage() {
const payload = {
chat_room: chat_room,
message: this.message
}
try {
const resp = await apiSendChatMessage(payload)
if (resp) {
await this.getChatMessages()
this.message = ""
}
} catch (err) {
console.log(err)
}
},
},
async mounted() {
await this.getTasksToDo()
await this.getTasksInProgress()
await this.getTasksDone()
await this.getChatRoomsLists()
if (chat_room) {
await this.getChatMessages()
}
this.isPageLoading = false;
},
created() {
this.isPageLoading = true
},
computed: {
formattedDate() {
const options = {
weekday: 'long',
month: 'long',
day: 'numeric',
year: 'numeric',
};
return this.currentDate.toLocaleDateString('en-US', options);
},
},
}).mount('#layout')
async function apiGetTasksToDo() {
try {
const resp = await fetch(`/api/tasks/todo`, {
method: 'GET'
}).then(resp => resp.json())
return resp
} catch (error) {
console.log(error)
return []
}
}
async function apiGetTasksInProgress() {
try {
const resp = await fetch(`/api/tasks/in-progress`, {
method: 'GET'
}).then(resp => resp.json())
return resp
} catch (error) {
console.log(error)
return []
}
}
async function apiGetTasksDone() {
try {
const resp = await fetch(`/api/tasks/done`, {
method: 'GET'
}).then(resp => resp.json())
return resp
} catch (error) {
console.log(error)
return []
}
}
async function apiMoveTasks(payload) {
try {
const resp = await fetch(`/api/tasks/move`, {
method: 'POST',
body: JSON.stringify(payload),
headers: {"Content-Type": "application/json"},
}).then(resp => resp.json())
return resp
} catch (error) {
console.log(error)
return []
}
}
async function apiAIGenerate(payload) {
try {
const resp = await fetch(`/api/ai/generate`, {
method: 'POST',
body: JSON.stringify(payload),
headers: {"Content-Type": "application/json"},
}).then(resp => resp.json())
return resp
} catch (error) {
console.log(error)
return []
}
}
async function apiAddTasks(payload) {
try {
const resp = await fetch(`/api/tasks/add`, {
method: 'POST',
body: JSON.stringify(payload),
headers: {"Content-Type": "application/json"},
}).then(resp => resp.json())
return resp
} catch (error) {
console.log(error)
return []
}
}
async function apiCreateNewChatRoom(payload) {
try {
const resp = await fetch(`/api/chat/room/add`, {
method: 'POST',
body: JSON.stringify(payload),
headers: {"Content-Type": "application/json"},
}).then(resp => resp.json())
return resp
} catch (error) {
console.log(error)
return []
}
}
async function apiChatRoomList() {
try {
const resp = await fetch(`/api/chat/rooms`, {
method: 'GET'
}).then(resp => resp.json())
return resp
} catch (error) {
console.log(error)
return []
}
}
async function apiGetChatMessages() {
try {
const resp = await fetch(`/api/chat/messages/${chat_room}`, {
method: 'GET'
}).then(resp => resp.json())
return resp
} catch (error) {
console.log(error)
return []
}
}
async function apiSendChatMessage(payload) {
try {
const resp = await fetch(`/api/chat/message/send`, {
method: 'POST',
body: JSON.stringify(payload),
headers: {"Content-Type": "application/json"},
}).then(resp => resp.json())
return resp
} catch (error) {
console.log(error)
return []
}
}
</script>
<style>
[v-cloak] {
display: none;
}
.spinner {
width: 10px;
height: 10px;
border-radius: 50%;
border: 9px solid #474bff;
animation: spinner-bulqg1 0.8s infinite linear alternate,
spinner-oaa3wk 1.6s infinite linear;
}
@keyframes spinner-bulqg1 {
0% {
clip-path: polygon(50% 50%, 0 0, 50% 0%, 50% 0%, 50% 0%, 50% 0%, 50% 0%);
}
12.5% {
clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 0%, 100% 0%, 100% 0%);
}
25% {
clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 100% 100%, 100% 100%);
}
50% {
clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%);
}
62.5% {
clip-path: polygon(50% 50%, 100% 0, 100% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%);
}
75% {
clip-path: polygon(50% 50%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 50% 100%, 0% 100%);
}
100% {
clip-path: polygon(50% 50%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 0% 100%);
}
}
@keyframes spinner-oaa3wk {
0% {
transform: scaleY(1) rotate(0deg);
}
49.99% {
transform: scaleY(1) rotate(135deg);
}
50% {
transform: scaleY(-1) rotate(0deg);
}
100% {
transform: scaleY(-1) rotate(-135deg);
}
}
.loading-spinner {
width: 44.8px;
height: 44.8px;
animation: loading-spinner-y0fdc1 2s infinite ease;
transform-style: preserve-3d;
}
.loading-spinner > div {
background-color: rgba(71, 75, 255, 0.2);
height: 100%;
position: absolute;
width: 100%;
border: 2.2px solid #474bff;
}
.loading-spinner div:nth-of-type(1) {
transform: translateZ(-22.4px) rotateY(180deg);
}
.loading-spinner div:nth-of-type(2) {
transform: rotateY(-270deg) translateX(50%);
transform-origin: top right;
}
.loading-spinner div:nth-of-type(3) {
transform: rotateY(270deg) translateX(-50%);
transform-origin: center left;
}
.loading-spinner div:nth-of-type(4) {
transform: rotateX(90deg) translateY(-50%);
transform-origin: top center;
}
.loading-spinner div:nth-of-type(5) {
transform: rotateX(-90deg) translateY(50%);
transform-origin: bottom center;
}
.loading-spinner div:nth-of-type(6) {
transform: translateZ(22.4px);
}
@keyframes loading-spinner-y0fdc1 {
0% {
transform: rotate(45deg) rotateX(-25deg) rotateY(25deg);
}
50% {
transform: rotate(45deg) rotateX(-385deg) rotateY(25deg);
}
100% {
transform: rotate(45deg) rotateX(-385deg) rotateY(385deg);
}
}
</style>
{% endblock %}Editor is loading...
Leave a Comment