Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@
>
<router-view />
<FeedbackButton />
<Popover />
</div>
</template>

<script lang="ts">
import * as api from './api/';

Check warning on line 14 in src/App.vue

View workflow job for this annotation

GitHub Actions / ESlint

Can't resolve './api/' in '/home/runner/work/hawk.garage/hawk.garage/src'
import { setLanguage } from './i18n';

Check warning on line 15 in src/App.vue

View workflow job for this annotation

GitHub Actions / ESlint

Can't resolve './i18n' in '/home/runner/work/hawk.garage/hawk.garage/src'
import { defineComponent } from 'vue';
import FeedbackButton from './components/utils/FeedbackButton.vue';
import notifier from 'codex-notifier';
import { Popover } from '@codexteam/ui/vue';

export default defineComponent({
name: 'App',
components: {
FeedbackButton,
Popover,
},
computed: {
/**
Expand Down
17 changes: 16 additions & 1 deletion src/api/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
QUERY_EVENT,
QUERY_EVENT_REPETITIONS_PORTION,
QUERY_PROJECT_DAILY_EVENTS,
QUERY_CHART_DATA
QUERY_CHART_DATA,
MUTATION_REMOVE_EVENT
} from './queries';
import * as api from '@/api';
import type {
Expand Down Expand Up @@ -199,3 +200,17 @@ export async function fetchChartData(
timezoneOffset,
})).project.event.chartData;
}

/**
* Remove event and all related data (repetitions, daily events)
* @param projectId - project event is related to
* @param eventId — original event id to remove
*/
export async function removeEvent(projectId: string, eventId: string): Promise<APIResponse<{ removeEvent: boolean }>> {
return await api.call<{ removeEvent: boolean }>(MUTATION_REMOVE_EVENT, {
projectId,
eventId,
}, undefined, {
allowErrors: true,
});
}
10 changes: 10 additions & 0 deletions src/api/events/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,13 @@ export const MUTATION_REMOVE_EVENT_ASSIGNEE = `
}
}
`;

// language=GraphQL
/**
* GraphQL Mutation to remove an event and all related data
*/
export const MUTATION_REMOVE_EVENT = `
mutation removeEvent($projectId: ID!, $eventId: ID!) {
removeEvent(projectId: $projectId, eventId: $eventId)
}
`;
103 changes: 103 additions & 0 deletions src/components/event/EventActionsMenu.vue
Comment thread
quangtuanitmo18 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<template>
<ContextMenu
class="event-actions-menu"
:items="menuItems"
/>
</template>

<script setup lang="ts">
import { REMOVE_EVENT } from '@/store/modules/events/actionTypes';
import { ContextMenu, type ContextMenuItem } from '@codexteam/ui/vue';
import notifier from 'codex-notifier';
import { computed, getCurrentInstance } from 'vue';
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
import { i18n } from '../../i18n';
import { ActionType } from '../utils/ConfirmationWindow/types';

interface Props {
/**
* Project id the event belongs to
*/
projectId: string;
/**
* Original event id to remove
*/
eventId: string;
/**
* Callback to close popover
*/
onClose?: () => void;
}

const props = defineProps<Props>();

const store = useStore();
const router = useRouter();
const instance = getCurrentInstance();

/**
* Show confirmation dialog and remove event on confirm
*/
function confirmRemoveEvent(): void {
if (!instance?.appContext.config.globalProperties.$confirm) {
return;
}

instance.appContext.config.globalProperties.$confirm.open({
description: i18n.global.t('event.removeConfirmation').toString(),
actionType: ActionType.DELETION,
continueButtonText: i18n.global.t('event.removeButton').toString(),
onConfirm: async () => {
const isRemoved = await store.dispatch(REMOVE_EVENT, {
projectId: props.projectId,
eventId: props.eventId,
});

if (isRemoved) {
notifier.show({
message: i18n.global.t('event.removeSuccess').toString(),
style: 'success',
time: 5000,
});
router.push({
name: 'project-overview',
params: { projectId: props.projectId },
query: { reload: String(Date.now()) },
});

return;
}

notifier.show({
message: i18n.global.t('errors.Something went wrong').toString(),
style: 'error',
time: 5000,
});
},
});
}

/**
* Actions available in event context menu
*/
const menuItems = computed<ContextMenuItem[]>(() => {
return [
{
type: 'default',
title: i18n.global.t('event.remove') as string,
icon: 'Trash',
onActivate: () => {
props.onClose?.();
confirmRemoveEvent();
},
},
];
});
</script>

<style scoped>
Comment thread
neSpecc marked this conversation as resolved.
.event-actions-menu {
min-width: 140px;
}
</style>
76 changes: 69 additions & 7 deletions src/components/event/EventHeader.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
<template>
<div class="event-header">
<div class="event-layout__container">
<span
v-if="!loading"
class="event-header__date"
>
{{ formattedFullDate }}
</span>
<div class="event-header__top-right">
<span
v-if="!loading"
class="event-header__date"
>
{{ formattedFullDate }}
</span>
<CdxButton
v-if="isAdmin"
class="event-header__button--more"
secondary
icon="EtcHorisontal"
@click="onMoreClick"
/>
</div>

<div
v-if="workspace"
Expand Down Expand Up @@ -130,10 +139,14 @@ import { Workspace } from '@/types/workspaces';
import { projectBadges } from '../../mixins/projectBadges';
import ProjectBadge from '../project/ProjectBadge.vue';
import { JavaScriptAddons } from '@hawk.so/types';
import { Button as CdxButton, usePopover } from '@codexteam/ui/vue';
import EventActionsMenu from './EventActionsMenu.vue';
import Icon from '../utils/Icon.vue';

export default defineComponent({
name: 'EventHeader',
components: {
CdxButton,
TabBar,
ViewedBy,
UiButton,
Expand All @@ -153,6 +166,14 @@ export default defineComponent({
validator: prop => typeof prop === 'object' || prop === null,
},
},
setup() {
const { showPopover, hide } = usePopover();

return {
showPopover,
hidePopover: hide,
};
},
data() {
return {
/**
Expand Down Expand Up @@ -253,6 +274,13 @@ export default defineComponent({
return this.$store.getters.getWorkspaceByProjectId(this.projectId);
},

/**
* Is current user admin in workspace with this project
*/
isAdmin(): boolean {
return this.workspace ? this.$store.getters.isCurrentUserAdmin(this.workspace.id) : false;
},

/**
* Computed property that returns formatted full date for event timestamp
*/
Expand Down Expand Up @@ -302,6 +330,33 @@ export default defineComponent({
window.open(this.event.taskManagerItem.url, '_blank', 'noopener');
}
},

/**
* Open the "more options" context menu near the 3-dot button
*
* @param event - native click mouse event
*/
onMoreClick(event: MouseEvent) {
if (!this.isAdmin) {
return;
}

this.showPopover({
targetEl: event.currentTarget as HTMLElement,
with: {
Comment thread
quangtuanitmo18 marked this conversation as resolved.
component: EventActionsMenu,
props: {
projectId: this.projectId,
eventId: this.$route.params.eventId,
onClose: () => this.hidePopover(),
},
},
align: {
vertically: 'below',
horizontally: 'right',
},
});
},
},
});
</script>
Expand Down Expand Up @@ -352,8 +407,14 @@ export default defineComponent({
text-overflow: ellipsis;
}

&__date {
&__top-right {
display: flex;
float: right;
align-items: center;
gap: 12px;
}

&__date {
font-size: 12px;
line-height: 23px;
}
Expand Down Expand Up @@ -395,6 +456,7 @@ export default defineComponent({
&:hover {
color: var(--color-text-main)
}

}

&__nav-bar, &__viewed-by {
Expand Down
13 changes: 13 additions & 0 deletions src/components/project/Overview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ export default {
return this.workspace?.isBlocked;
},
},
watch: {
/**
* Refresh list when route gets a new reload token
*
* @param newValue
* @param oldValue
*/
'$route.query.reload'(newValue, oldValue) {
if (newValue && newValue !== oldValue) {
this.reloadDailyEvents();
}
Comment thread
neSpecc marked this conversation as resolved.
},
},

/**
* Vue created hook
Expand Down
6 changes: 5 additions & 1 deletion src/i18n/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,11 @@
"title": "Event Hidden",
"description": "We received this event but have hidden it because your subscription has expired.",
"upgradeButton": "Pay and view event"
}
},
"remove": "Remove event",
"removeConfirmation": "Are you sure you want to remove this event? All related data will also be removed.",
"removeButton": "Remove",
"removeSuccess": "Event successfully removed"
},
"common": {
"workspace": "Workspace",
Expand Down
6 changes: 5 additions & 1 deletion src/i18n/messages/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,11 @@
"title": "Событие скрыто",
"description": "Мы получили это событие, но скрыли его, потому что ваша подписка закончилась.",
"upgradeButton": "Оплатить и посмотреть событие"
}
},
"remove": "Удалить событие",
"removeConfirmation": "Вы уверены, что хотите удалить это событие? Все связанные данные также будут удалены.",
"removeButton": "Удалить",
"removeSuccess": "Событие успешно удалено"
},
"common": {
"workspace": "Воркспейc",
Expand Down
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import '@codexteam/ui/styles';
import './styles/base.css';
import { createApp } from 'vue';
import 'virtual:svg-icons-register';
Expand All @@ -11,7 +12,6 @@ import * as api from './api/index';
import { REFRESH_TOKENS } from './store/modules/user/actionTypes';
import { RESET_STORE } from './store/methodsTypes';

import '@codexteam/ui/styles';

const DEBOUNCE_TIMEOUT = 1000;

Expand Down
5 changes: 5 additions & 0 deletions src/store/modules/events/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,8 @@ export const GET_CHART_DATA = 'GET_CHART_DATA';
* Get list project with dailyEvents portion
*/
export const FETCH_PROJECT_OVERVIEW = 'FETCH_PROJECT_OVERVIEW';

/**
* Remove a single event and all related data
*/
export const REMOVE_EVENT = 'REMOVE_EVENT';
Loading
Loading