diff --git a/src/App.vue b/src/App.vue index ba923701f..74f643f91 100644 --- a/src/App.vue +++ b/src/App.vue @@ -6,6 +6,7 @@ > + @@ -15,11 +16,13 @@ import { setLanguage } from './i18n'; 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: { /** diff --git a/src/api/events/index.ts b/src/api/events/index.ts index 1296e257a..c46f0604c 100644 --- a/src/api/events/index.ts +++ b/src/api/events/index.ts @@ -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 { @@ -196,3 +197,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> { + return await api.call<{ removeEvent: boolean }>(MUTATION_REMOVE_EVENT, { + projectId, + eventId, + }, undefined, { + allowErrors: true, + }); +} diff --git a/src/api/events/queries.ts b/src/api/events/queries.ts index 1c7a7ab61..f8f950fb6 100644 --- a/src/api/events/queries.ts +++ b/src/api/events/queries.ts @@ -156,3 +156,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) + } +`; diff --git a/src/components/event/EventActionsMenu.vue b/src/components/event/EventActionsMenu.vue new file mode 100644 index 000000000..7f70ac26c --- /dev/null +++ b/src/components/event/EventActionsMenu.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/src/components/event/EventHeader.vue b/src/components/event/EventHeader.vue index 570fc80c6..17ed93a69 100644 --- a/src/components/event/EventHeader.vue +++ b/src/components/event/EventHeader.vue @@ -1,12 +1,20 @@ @@ -145,6 +150,10 @@ export default { this.$refs.eventsList.reloadDailyEvents(); } }, + + async eventDeleted() { + this.reloadDailyEvents(); + }, }, }; diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 24af27a5b..f67bc3e83 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -609,7 +609,12 @@ "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 repetitions and related data will also be removed.", + "removeButton": "Remove", + "removeSuccess": "Event successfully removed", + "removeError": "Failed to remove the event" }, "common": { "workspace": "Workspace", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index c8e936963..f50c898b9 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -609,7 +609,12 @@ "title": "Событие скрыто", "description": "Мы получили это событие, но скрыли его, потому что ваша подписка закончилась.", "upgradeButton": "Оплатить и посмотреть событие" - } + }, + "remove": "Удалить событие", + "removeConfirmation": "Вы уверены, что хотите удалить это событие? Все повторения и связанные данные также будут удалены.", + "removeButton": "Удалить", + "removeSuccess": "Событие успешно удалено", + "removeError": "Не удалось удалить событие" }, "common": { "workspace": "Воркспейc", diff --git a/src/main.ts b/src/main.ts index 8f4ef828c..87cd505e5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,3 +1,4 @@ +import '@codexteam/ui/styles'; import './styles/base.css'; import { createApp } from 'vue'; import 'virtual:svg-icons-register'; @@ -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; diff --git a/src/store/modules/events/actionTypes.js b/src/store/modules/events/actionTypes.js index 82d0f49e1..a6a86594b 100644 --- a/src/store/modules/events/actionTypes.js +++ b/src/store/modules/events/actionTypes.js @@ -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'; diff --git a/src/store/modules/events/index.ts b/src/store/modules/events/index.ts index 98200153c..c9e7ecfbf 100644 --- a/src/store/modules/events/index.ts +++ b/src/store/modules/events/index.ts @@ -9,7 +9,8 @@ import { TOGGLE_EVENT_MARK, UPDATE_EVENT_ASSIGNEE, VISIT_EVENT, - GET_CHART_DATA + GET_CHART_DATA, + REMOVE_EVENT } from './actionTypes'; import { RESET_STORE } from '../../methodsTypes'; import type { Module } from 'vuex'; @@ -27,6 +28,12 @@ import { } from '@/types/events'; import type { User } from '@/types/user'; import type { EventChartItem } from '@/types/chart'; +import { useErrorTracker } from '@/hawk'; + +/** + * Error tracking composable + */ +const { track } = useErrorTracker(); /** * Mutations enum for this module @@ -416,6 +423,41 @@ const module: Module = { } }, + /** + * Remove event and all related data (repetitions, daily events) + * @param context - vuex action context (not used) + * @param context.commit - standard Vuex commit function + * @param payload - vuex action payload + * @param payload.projectId - project event is related to + * @param payload.eventId - original event id to remove + */ + async [REMOVE_EVENT](context, { projectId, eventId }: { + projectId: string; + eventId: string; + }): Promise { + const response = await eventsApi.removeEvent(projectId, eventId); + + if (response.errors?.length) { + response.errors.forEach((apiError) => { + const apiErrorDetails = { + message: apiError.message, + path: apiError.path ? apiError.path.join('.') : '', + code: String(apiError.extensions?.code ?? ''), + }; + + track(new Error(apiError.message), { + projectId, + eventId, + errorDetails: apiErrorDetails, + }); + }); + + return false; + } + + return Boolean(response.data?.removeEvent); + }, + /** * Resets module state * @param commit - standard Vuex commit function diff --git a/src/styles/base.css b/src/styles/base.css index 053d44ae5..1373a1195 100644 --- a/src/styles/base.css +++ b/src/styles/base.css @@ -295,4 +295,11 @@ body .cdx-notify { --accent--solid-hover: var(--color-indicator-medium-dark); --accent--bg-secondary: color-mod(var(--color-indicator-medium) alpha(10%)); --accent--text-solid-foreground: var(--color-text-main); + + /* Override codexteam/ui popover z-index to appear above app modals */ + --z-popover: 10000; + + .codex-popover { + padding: 10px; + } }