Skip to content

Commit

Permalink
feat: category options
Browse files Browse the repository at this point in the history
- join all space
- leave all spaces
- edit category title
- delete space
- create new space in category
  • Loading branch information
netchampfaris committed Jan 8, 2025
1 parent 03b6306 commit f05b3e5
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 23 deletions.
50 changes: 47 additions & 3 deletions frontend/src/components/NewSpaceDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,16 @@
autocomplete="off"
/>
</div>

<div class="flex gap-2">
<div class="w-7 h-7"></div>
<div class="w-full">
<Autocomplete
placeholder="Category"
:options="categoryOptions"
v-model="selectedCategory"
/>
</div>
</div>
<div class="flex items-center space-x-2">
<div class="w-7 h-7"></div>
<div>
Expand All @@ -48,20 +57,55 @@
</Dialog>
</template>
<script setup lang="ts">
import { Dialog, ErrorMessage, FormControl, TextInput } from 'frappe-ui'
import { Dialog, ErrorMessage, FormControl, TextInput, Autocomplete } from 'frappe-ui'
import IconPicker from './IconPicker.vue'
import { useNewDoc } from 'frappe-ui/src/data-fetching'
import { GPProject } from '@/types/doctypes'
import { spaces } from '@/data/spaces'
import { computed, ref, watch } from 'vue'
import { activeTeams } from '@/data/teams'
const props = defineProps<{
category?: string
}>()
const show = defineModel<boolean>()
const newSpace = useNewDoc<GPProject>('GP Project', {
title: '',
icon: '',
is_private: false,
team: '',
is_private: 0,
})
const selectedCategory = ref<{ label: string; value: string }>(null as any)
watch(show, (value: boolean) => {
if (value) {
if (props.category) {
selectCategory(props.category)
} else {
selectedCategory.value = null as any
}
}
})
const categoryOptions = computed(() => {
return activeTeams.value.map((team) => ({
label: team.title,
value: team.name,
}))
})
function selectCategory(categoryId: string) {
let categoryOption = categoryOptions.value.find((option) => option.value === categoryId)
if (categoryOption) {
selectedCategory.value = categoryOption
}
}
function submit() {
if (selectedCategory.value) {
newSpace.doc.team = selectedCategory.value.value
}
newSpace.submit().then(() => {
// TODO: useNewDoc should automatically reload related resources
spaces.reload()
Expand Down
29 changes: 28 additions & 1 deletion frontend/src/components/SpaceOptions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { GPProject } from '@/types/doctypes'
import LucideUserPlus from '~icons/lucide/user-plus'
import LucideEdit from '~icons/lucide/edit'
import LucideMerge from '~icons/lucide/merge'
import LucideArchive from '~icons/lucide/archive'
import LucideTrash2 from '~icons/lucide/trash-2'
import LucideLogOut from '~icons/lucide/log-out'
Expand Down Expand Up @@ -66,7 +67,7 @@ const options = computed(() => [
},
{
label: 'Archive',
icon: LucideTrash2,
icon: LucideArchive,
onClick: () => {
createDialog({
title: 'Archive space',
Expand All @@ -91,5 +92,31 @@ const options = computed(() => [
},
condition: () => !space.value?.archived_at,
},
{
label: 'Delete',
icon: LucideTrash2,
onClick: () => {
let message = `This will permanently delete the space and all its content. This action cannot be undone.`
if (space.value?.discussions_count && space.value?.tasks_count) {
message = `This will permanently delete the space and all its content. This space has ${space.value?.discussions_count} discussions and ${space.value?.tasks_count} tasks. This action cannot be undone.`
} else if (space.value?.discussions_count) {
message = `This will permanently delete the space and all its content. This space has ${space.value?.discussions_count} discussions. This action cannot be undone.`
}
createDialog({
title: 'Delete space',
message,
actions: [
{
label: 'Delete',
theme: 'red',
variant: 'solid',
loading: spaces.delete.loading,
onClick: ({ close }) => spaces.delete.submit({ name: props.spaceId }).then(close),
},
],
})
},
condition: () => !space.value?.archived_at,
},
])
</script>
6 changes: 0 additions & 6 deletions frontend/src/data/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,6 @@ export let teams = useList<Team>({
cacheKey: 'Teams',
limit: 999,
immediate: true,
transform(data) {
for (let team of data) {
team.name = team.name.toString()
}
return data
},
})

export let activeTeams = computed(() => {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/pages/Page.vue
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ const updateUrlSlug = () => {
name: page.doc?.project ? 'SpacePage' : 'Page',
params: {
...route.params,
spaceId: page.doc?.project,
slug: page.doc?.slug,
},
query: route.query,
Expand Down
118 changes: 105 additions & 13 deletions frontend/src/pages/SpaceList.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,56 @@
<template>
<PageHeader>
<Breadcrumbs class="h-7" :items="[{ label: 'Spaces', route: { name: 'Spaces' } }]" />
<Button variant="solid" @click="newSpaceDialog = true">
<Button
variant="solid"
@click="
() => {
categoryForNewSpace = ''
newSpaceDialog = true
}
"
>
<template #prefix><LucidePlus class="h-4 w-4" /></template>
Add new
</Button>
</PageHeader>
<NewSpaceDialog v-model="newSpaceDialog" />
<NewSpaceDialog v-model="newSpaceDialog" :category="categoryForNewSpace" />
<div class="mx-auto max-w-3xl px-2 sm:px-5 pb-20 sm:pb-80">
<div class="mt-5 flex px-2.5">
<TabButtons :buttons="[{ label: 'Active' }, { label: 'Archived' }]" v-model="currentTab" />
</div>
<div v-for="group in groupedSpaces" :key="group.name">
<div class="p-3 pt-8 text-ink-gray-9 text-base">{{ group.title || group.name }}</div>
<div class="p-3 pt-8 flex items-center justify-between">
<div class="text-ink-gray-9 text-base">{{ group.title || group.name }}</div>
<DropdownMoreOptions
placement="right"
:options="[
{
label: 'Edit',
onClick: () => {
editCategoryDialog.category = group
editCategoryDialog.categoryTitle = group.title
editCategoryDialog.show = true
},
},
{
label: 'Join all',
onClick: () => joinSpaces(group.spaces.map((d) => d.name)),
},
{
label: 'Leave all',
onClick: () => leaveSpaces(group.spaces.map((d) => d.name)),
},
{
label: 'New space',
onClick: () => {
categoryForNewSpace = group.name
newSpaceDialog = true
},
},
]"
/>
</div>
<div class="border-b mx-3"></div>
<router-link
v-for="(d, index) in group.spaces"
Expand Down Expand Up @@ -41,12 +79,13 @@
</span>
</div>
</div>
<div class="mt-1 flex min-w-0 items-center justify-between">
<div class="mt-1 flex min-w-0 items-center">
<div
class="overflow-hidden text-ellipsis whitespace-nowrap text-base text-ink-gray-5"
>
<span>
{{ d.members.length }} {{ d.members.length == 1 ? 'member' : 'members' }}
{{ d.discussions_count }}
{{ d.discussions_count == 1 ? 'post' : 'posts' }}
</span>
</div>
</div>
Expand Down Expand Up @@ -76,38 +115,69 @@
</div>
</div>
</div>
<!-- <div
class="mx-3 h-px border-t border-outline-gray-modals"
v-if="index < projects.data.length - 1"
></div> -->
</router-link>
</div>
</div>
<Dialog :options="{ title: 'Change category title' }" v-model="editCategoryDialog.show">
<template #body-content>
<FormControl label="Title" v-model="editCategoryDialog.categoryTitle" />
</template>
<template #actions>
<div class="flex justify-end">
<Button
variant="solid"
@click="
() =>
teams.setValue
.submit({
name: editCategoryDialog.category.name,
title: editCategoryDialog.categoryTitle,
})
.then(() => {
editCategoryDialog.show = false
editCategoryDialog.category = null
editCategoryDialog.categoryTitle = ''
})
"
>
Submit
</Button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { reactive, ref } from 'vue'
import { Breadcrumbs, TabButtons } from 'frappe-ui'
import { useDoctype } from 'frappe-ui/src/data-fetching'
import { useGroupedSpaces } from '@/data/groupedSpaces'
import { useSessionUser } from '@/data/users'
import { hasJoined, joinedSpaces } from '@/data/spaces'
import NewSpaceDialog from '@/components/NewSpaceDialog.vue'
import SpaceOptions from '@/components/SpaceOptions.vue'
import PageHeader from '@/components/PageHeader.vue'
import { hasJoined, joinedSpaces } from '@/data/spaces'
import DropdownMoreOptions from '@/components/DropdownMoreOptions.vue'
import { GPProject } from '@/types/GPProject'
import { GPTeam } from '@/types/doctypes'
import LucideLock from '~icons/lucide/lock'
const sessionUser = useSessionUser()
const teams = useDoctype<GPTeam>('GP Team')
const currentTab = ref('Active')
const categoryForNewSpace = ref('')
const groupedSpaces = useGroupedSpaces({
filterFn: (space) =>
Boolean(currentTab.value == 'Active' ? !space.archived_at : space.archived_at),
})
const newSpaceDialog = ref(false)
const editCategoryDialog = reactive({
category: null,
categoryTitle: '',
show: false,
})
let spaces = useDoctype<GPProject>('GP Project')
// spaces.runMethod.submit({})
function joinSpace(space) {
spaces.runDocMethod
Expand All @@ -118,6 +188,17 @@ function joinSpace(space) {
.then(joinedSpaces.reload)
}
function joinSpaces(spaceIds: string[]) {
return spaces.runMethod
.submit({
method: 'join_spaces',
params: {
spaces: spaceIds,
},
})
.then(joinedSpaces.reload)
}
function leaveSpace(space) {
spaces.runDocMethod
.submit({
Expand All @@ -127,6 +208,17 @@ function leaveSpace(space) {
.then(joinedSpaces.reload)
}
function leaveSpaces(spaceIds: string[]) {
return spaces.runMethod
.submit({
method: 'leave_spaces',
params: {
spaces: spaceIds,
},
})
.then(joinedSpaces.reload)
}
function unarchiveSpace(space) {
spaces.runDocMethod.submit({
method: 'unarchive',
Expand Down
8 changes: 8 additions & 0 deletions gameplan/gameplan/doctype/gp_project/gp_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,11 @@ def join_spaces(spaces: list[str] = None):
return
for space in spaces:
frappe.get_doc("GP Project", space).join()


@frappe.whitelist()
def leave_spaces(spaces: list[str] = None):
if not spaces:
return
for space in spaces:
frappe.get_doc("GP Project", space).leave()

0 comments on commit f05b3e5

Please sign in to comment.