forked from vikunja/vikunja
feat(calendar): initial commit
This commit is contained in:
parent
f0d695e789
commit
beac161ef8
|
@ -54,6 +54,10 @@
|
|||
"@fortawesome/free-regular-svg-icons": "6.5.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.5.1",
|
||||
"@fortawesome/vue-fontawesome": "3.0.6",
|
||||
"@fullcalendar/core": "^6.1.11",
|
||||
"@fullcalendar/interaction": "^6.1.11",
|
||||
"@fullcalendar/timegrid": "^6.1.11",
|
||||
"@fullcalendar/vue3": "^6.1.11",
|
||||
"@github/hotkey": "3.1.0",
|
||||
"@infectoone/vue-ganttastic": "2.3.1",
|
||||
"@intlify/unplugin-vue-i18n": "3.0.1",
|
||||
|
|
|
@ -25,6 +25,18 @@ dependencies:
|
|||
'@fortawesome/vue-fontawesome':
|
||||
specifier: 3.0.6
|
||||
version: 3.0.6(@fortawesome/fontawesome-svg-core@6.5.1)(vue@3.4.21)
|
||||
'@fullcalendar/core':
|
||||
specifier: ^6.1.11
|
||||
version: 6.1.11
|
||||
'@fullcalendar/interaction':
|
||||
specifier: ^6.1.11
|
||||
version: 6.1.11(@fullcalendar/core@6.1.11)
|
||||
'@fullcalendar/timegrid':
|
||||
specifier: ^6.1.11
|
||||
version: 6.1.11(@fullcalendar/core@6.1.11)
|
||||
'@fullcalendar/vue3':
|
||||
specifier: ^6.1.11
|
||||
version: 6.1.11(@fullcalendar/core@6.1.11)(vue@3.4.21)
|
||||
'@github/hotkey':
|
||||
specifier: 3.1.0
|
||||
version: 3.1.0(patch_hash=c67tdk7qpd5grxd2zj6lsxfbou)
|
||||
|
@ -2693,6 +2705,47 @@ packages:
|
|||
vue: 3.4.21(typescript@5.4.2)
|
||||
dev: false
|
||||
|
||||
/@fullcalendar/core@6.1.11:
|
||||
resolution: {integrity: sha512-TjG7c8sUz+Vkui2FyCNJ+xqyu0nq653Ibe99A66LoW95oBo6tVhhKIaG1Wh0GVKymYiqAQN/OEdYTuj4ay27kA==}
|
||||
dependencies:
|
||||
preact: 10.12.1
|
||||
dev: false
|
||||
|
||||
/@fullcalendar/daygrid@6.1.11(@fullcalendar/core@6.1.11):
|
||||
resolution: {integrity: sha512-hF5jJB7cgUIxWD5MVjj8IU407HISyLu7BWXcEIuTytkfr8oolOXeCazqnnjmRbnFOncoJQVstTtq6SIhaT32Xg==}
|
||||
peerDependencies:
|
||||
'@fullcalendar/core': ~6.1.11
|
||||
dependencies:
|
||||
'@fullcalendar/core': 6.1.11
|
||||
dev: false
|
||||
|
||||
/@fullcalendar/interaction@6.1.11(@fullcalendar/core@6.1.11):
|
||||
resolution: {integrity: sha512-ynOKjzuPwEAMgTQ6R/Z2zvzIIqG4p8/Qmnhi1q0vzPZZxSIYx3rlZuvpEK2WGBZZ1XEafDOP/LGfbWoNZe+qdg==}
|
||||
peerDependencies:
|
||||
'@fullcalendar/core': ~6.1.11
|
||||
dependencies:
|
||||
'@fullcalendar/core': 6.1.11
|
||||
dev: false
|
||||
|
||||
/@fullcalendar/timegrid@6.1.11(@fullcalendar/core@6.1.11):
|
||||
resolution: {integrity: sha512-0seUHK/ferH89IeuCvV4Bib0zWjgK0nsptNdmAc9wDBxD/d9hm5Mdti0URJX6bDoRtsSfRDu5XsRcrzwoc+AUQ==}
|
||||
peerDependencies:
|
||||
'@fullcalendar/core': ~6.1.11
|
||||
dependencies:
|
||||
'@fullcalendar/core': 6.1.11
|
||||
'@fullcalendar/daygrid': 6.1.11(@fullcalendar/core@6.1.11)
|
||||
dev: false
|
||||
|
||||
/@fullcalendar/vue3@6.1.11(@fullcalendar/core@6.1.11)(vue@3.4.21):
|
||||
resolution: {integrity: sha512-jBoDS0WSpuOM9ZgjL3lNh6o385u/LthFZDaMUACjVVJZh3JuBbuA7ghdUvIelcTNXa5VRCkSZOpivTJWOnLfcg==}
|
||||
peerDependencies:
|
||||
'@fullcalendar/core': ~6.1.11
|
||||
vue: ^3.0.11
|
||||
dependencies:
|
||||
'@fullcalendar/core': 6.1.11
|
||||
vue: 3.4.21(typescript@5.4.2)
|
||||
dev: false
|
||||
|
||||
/@github/hotkey@3.1.0(patch_hash=c67tdk7qpd5grxd2zj6lsxfbou):
|
||||
resolution: {integrity: sha512-Lj9QjYa+b+Nk5U1nZtlXLdx3HI8/EeM6ZNwBjpYcGVYqpwHdM2ScRH0p7+5zh28JG6SPbTM9+Rb1dFd742qMTw==}
|
||||
dev: false
|
||||
|
@ -8483,6 +8536,10 @@ packages:
|
|||
picocolors: 1.0.0
|
||||
source-map-js: 1.2.0
|
||||
|
||||
/preact@10.12.1:
|
||||
resolution: {integrity: sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==}
|
||||
dev: false
|
||||
|
||||
/prelude-ls@1.1.2:
|
||||
resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
<template>
|
||||
<ProjectWrapper
|
||||
ref="projectWrapper"
|
||||
class="project-list"
|
||||
:project-id="projectId"
|
||||
:view-id
|
||||
>
|
||||
<template #header>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<FullCalendar :options="calendarOptions" ref="calendar"/>
|
||||
</template>
|
||||
|
||||
</ProjectWrapper>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
|
||||
|
||||
import {ref, computed, nextTick, onMounted, watch, type Ref} from 'vue'
|
||||
import draggable from 'zhyswan-vuedraggable'
|
||||
import {isSavedFilter} from '@/services/savedFilter'
|
||||
import ProjectWrapper from '@/components/project/ProjectWrapper.vue'
|
||||
import FullCalendar from '@fullcalendar/vue3'
|
||||
import timeGridPlugin from '@fullcalendar/timegrid'
|
||||
import interactionPlugin from '@fullcalendar/interaction'
|
||||
import type { IProject } from '@/modelTypes/IProject'
|
||||
import type { IProjectView } from '@/modelTypes/IProjectView'
|
||||
|
||||
import { defineComponent } from 'vue'
|
||||
import type { ITask } from '@/modelTypes/ITask'
|
||||
import { useTaskList } from '@/composables/useTaskList'
|
||||
import { elementClosest } from '@fullcalendar/core/internal'
|
||||
|
||||
|
||||
const calendar = ref(null)
|
||||
const projectWrapper = ref(null)
|
||||
|
||||
const {
|
||||
projectId,
|
||||
viewId,
|
||||
} = defineProps<{
|
||||
projectId: IProject['id'],
|
||||
viewId: IProjectView['id'],
|
||||
}>()
|
||||
const {
|
||||
tasks: allTasks,
|
||||
loading,
|
||||
totalPages,
|
||||
currentPage,
|
||||
loadTasks,
|
||||
params,
|
||||
sortByParam,
|
||||
} = useTaskList(() => projectId, () => viewId)
|
||||
|
||||
const tasks = ref<ITask[]>([])
|
||||
|
||||
|
||||
let eventGuid = 0
|
||||
let todayStr = new Date().toISOString().replace(/T.*$/, '') // YYYY-MM-DD of today
|
||||
|
||||
/*
|
||||
onMounted(() => {
|
||||
if(calendar.value)
|
||||
calendar.value.calendar.addEvent({
|
||||
id: createEventId(),
|
||||
title: 'All-day event',
|
||||
start: todayStr
|
||||
});
|
||||
})
|
||||
*/
|
||||
|
||||
let initialEvents = [];
|
||||
|
||||
watch(
|
||||
allTasks,
|
||||
() => {
|
||||
console.log(allTasks.value);
|
||||
for(let task of allTasks.value) {
|
||||
if(task.startDate)
|
||||
calendar.value.calendar.addEvent({
|
||||
id: task.id,
|
||||
title: task.title,
|
||||
start: task.startDate,
|
||||
end: task.endDate,
|
||||
extendedProps: {
|
||||
done: task.done
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
/*
|
||||
const INITIAL_EVENTS = [
|
||||
{
|
||||
id: createEventId(),
|
||||
title: 'All-day event',
|
||||
start: todayStr
|
||||
},
|
||||
{
|
||||
id: createEventId(),
|
||||
title: 'Timed event',
|
||||
start: todayStr + 'T12:00:00'
|
||||
}
|
||||
]*/
|
||||
|
||||
function createEventId() {
|
||||
return String(eventGuid++)
|
||||
}
|
||||
|
||||
/*
|
||||
const {
|
||||
projectId,
|
||||
viewId,
|
||||
} = defineProps<{
|
||||
projectId: IProject['id'],
|
||||
viewId: IProjectView['id'],
|
||||
}>()
|
||||
*/
|
||||
|
||||
|
||||
function handleWeekendsToggle() {
|
||||
calendarOptions.value.weekends = calendarOptions.value.weekends // update a property
|
||||
};
|
||||
|
||||
function handleDateSelect(selectInfo: { view: { calendar: any }; startStr: any; endStr: any; allDay: any }) {
|
||||
|
||||
|
||||
//let title = prompt('Please enter a new title for your event')
|
||||
let title = "test"
|
||||
let calendarApi = selectInfo.view.calendar
|
||||
|
||||
calendarApi.unselect() // clear date selection
|
||||
|
||||
if (title) {
|
||||
calendarApi.addEvent({
|
||||
id: createEventId(),
|
||||
title,
|
||||
start: selectInfo.startStr,
|
||||
end: selectInfo.endStr,
|
||||
allDay: selectInfo.allDay
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
function handleEventClick(clickInfo: { event: any }) {
|
||||
if (confirm(`Are you sure you want to delete the event '${clickInfo.event.title}'`)) {
|
||||
clickInfo.event.extendedProps.done != clickInfo.event.extendedProps.done
|
||||
console.log("here")
|
||||
clickInfo.view.calendar.render()
|
||||
}
|
||||
};
|
||||
|
||||
function parseTime(time: Date | null) {
|
||||
if(time)
|
||||
return time.getHours() + ":" + String(time.getMinutes()).padStart(2,0)
|
||||
else
|
||||
return ""
|
||||
}
|
||||
|
||||
function eventRender (info: any) {
|
||||
console.log(info);
|
||||
const event = info.event;
|
||||
console.log(event.extendedProps);
|
||||
let timeEl = document.createElement("p");
|
||||
let titleEl = document.createElement("p");
|
||||
timeEl.innerHTML = parseTime(event.start)
|
||||
titleEl.innerHTML = event.title
|
||||
if(event.end)
|
||||
timeEl.innerHTML += " - " + parseTime(event.end);
|
||||
|
||||
if(event.extendedProps.done) {
|
||||
let strikethroughEl = document.createElement("s");
|
||||
strikethroughEl.innerHTML = titleEl.innerHTML;
|
||||
titleEl = strikethroughEl;
|
||||
}
|
||||
|
||||
return {domNodes: [titleEl, timeEl]}
|
||||
// {description: "Lecture", department: "BioChemistry"}
|
||||
}
|
||||
|
||||
const calendarOptions = ref({
|
||||
plugins: [
|
||||
timeGridPlugin,
|
||||
interactionPlugin // needed for dateClick
|
||||
],
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'timeGridWeek,timeGridDay'
|
||||
},
|
||||
initialView: 'timeGridWeek',
|
||||
initialEvents: initialEvents, // alternatively, use the `events` setting to fetch from a feed
|
||||
editable: true,
|
||||
selectable: true,
|
||||
selectMirror: true,
|
||||
dayMaxEvents: true,
|
||||
weekends: true,
|
||||
select: handleDateSelect,
|
||||
eventClick: handleEventClick,
|
||||
eventContent: eventRender
|
||||
/* you can update a remote database when these fire:
|
||||
eventAdd:
|
||||
eventChange:
|
||||
eventRemove:
|
||||
*/
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
|
@ -116,6 +116,9 @@ function validateTitle() {
|
|||
<option value="kanban">
|
||||
{{ $t('project.kanban.title') }}
|
||||
</option>
|
||||
<option value="calendar">
|
||||
{{ $t('project.calendar.title') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -362,6 +362,9 @@
|
|||
"bucketLimitSavedSuccess": "The bucket limit been saved successfully.",
|
||||
"collapse": "Collapse this bucket"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Calendar"
|
||||
},
|
||||
"pseudo": {
|
||||
"favorites": {
|
||||
"title": "Favorites"
|
||||
|
|
|
@ -7,6 +7,7 @@ import ProjectList from '@/components/project/views/ProjectList.vue'
|
|||
import ProjectGantt from '@/components/project/views/ProjectGantt.vue'
|
||||
import ProjectTable from '@/components/project/views/ProjectTable.vue'
|
||||
import ProjectKanban from '@/components/project/views/ProjectKanban.vue'
|
||||
import ProjectCalendar from '@/components/project/views/ProjectCalendar.vue'
|
||||
|
||||
const {
|
||||
projectId,
|
||||
|
@ -77,4 +78,9 @@ const route = useRoute()
|
|||
:project-id="projectId"
|
||||
:view-id
|
||||
/>
|
||||
<ProjectCalendar
|
||||
v-if="currentView?.viewKind === 'calendar'"
|
||||
:project-id="projectId"
|
||||
:view-id
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -31,10 +31,12 @@ const viewToEdit = ref<IProjectView | null>(null)
|
|||
async function createView() {
|
||||
if (!showCreateForm.value) {
|
||||
showCreateForm.value = true
|
||||
alert(1)
|
||||
return
|
||||
}
|
||||
|
||||
if (newView.value.title === '') {
|
||||
alert(2)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -50,6 +52,7 @@ async function createView() {
|
|||
projectStore.setProjectView(result)
|
||||
newView.value = new ProjectViewModel({})
|
||||
} catch (e) {
|
||||
alert(e)
|
||||
error(e)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@ func (p *ProjectViewKind) MarshalJSON() ([]byte, error) {
|
|||
return []byte(`"table"`), nil
|
||||
case ProjectViewKindKanban:
|
||||
return []byte(`"kanban"`), nil
|
||||
case ProjectViewKindCalendar:
|
||||
return []byte(`"calendar"`), nil
|
||||
}
|
||||
|
||||
return []byte(`null`), nil
|
||||
|
@ -58,6 +60,8 @@ func (p *ProjectViewKind) UnmarshalJSON(bytes []byte) error {
|
|||
*p = ProjectViewKindTable
|
||||
case "kanban":
|
||||
*p = ProjectViewKindKanban
|
||||
case "calendar":
|
||||
*p = ProjectViewKindKanban
|
||||
default:
|
||||
return fmt.Errorf("unknown project view kind: %s", value)
|
||||
}
|
||||
|
@ -70,6 +74,7 @@ const (
|
|||
ProjectViewKindGantt
|
||||
ProjectViewKindTable
|
||||
ProjectViewKindKanban
|
||||
ProjectViewKindCalendar
|
||||
)
|
||||
|
||||
type BucketConfigurationModeKind int
|
||||
|
@ -420,6 +425,7 @@ func CreateDefaultViewsForProject(s *xorm.Session, project *Project, a web.Auth,
|
|||
return
|
||||
}
|
||||
|
||||
|
||||
kanban := &ProjectView{
|
||||
ProjectID: project.ID,
|
||||
Title: "Kanban",
|
||||
|
@ -432,11 +438,24 @@ func CreateDefaultViewsForProject(s *xorm.Session, project *Project, a web.Auth,
|
|||
return
|
||||
}
|
||||
|
||||
calendar := &ProjectView{
|
||||
ProjectID: project.ID,
|
||||
Title: "Calendar",
|
||||
ViewKind: ProjectViewKindCalendar,
|
||||
Position: 500,
|
||||
}
|
||||
err = createProjectView(s, calendar, a, createBacklogBucket)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
project.Views = []*ProjectView{
|
||||
list,
|
||||
gantt,
|
||||
table,
|
||||
kanban,
|
||||
calendar,
|
||||
}
|
||||
|
||||
return
|
||||
|
|
Loading…
Reference in New Issue