feat(calendar): replace CSS-transform zoom with GSAP flying-rect animation and scope data to year
- Add CalendarDateRange input to GraphQL schema; server resolver now accepts from/to params - Frontend query sends year-scoped date range variables reactively - Rewrite zoom-in/zoom-out animations using GSAP flying-rect overlay (650ms vs 2400ms) - Add flying-rect element to CrmCalendarPanel with proper CSS - Remove old calendarSceneTransformStyle CSS-transition approach - Add calendarKillTweens cleanup in onBeforeUnmount Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import gsap from "gsap";
|
||||
import { nextTick, onBeforeUnmount, onMounted } from "vue";
|
||||
import CrmAuthLoading from "~~/app/components/workspace/auth/CrmAuthLoading.vue";
|
||||
import CrmCalendarPanel from "~~/app/components/workspace/calendar/CrmCalendarPanel.vue";
|
||||
@@ -596,9 +597,14 @@ const { result: contactInboxesResult, refetch: refetchContactInboxes } = useQuer
|
||||
{ enabled: apolloAuthReady },
|
||||
);
|
||||
|
||||
const calendarQueryYear = ref(new Date().getFullYear());
|
||||
const calendarQueryVars = computed(() => ({
|
||||
from: new Date(calendarQueryYear.value, 0, 1).toISOString(),
|
||||
to: new Date(calendarQueryYear.value + 1, 0, 1).toISOString(),
|
||||
}));
|
||||
const { result: calendarResult, refetch: refetchCalendar } = useQuery(
|
||||
CalendarQueryDocument,
|
||||
null,
|
||||
calendarQueryVars,
|
||||
{ enabled: apolloAuthReady },
|
||||
);
|
||||
|
||||
@@ -2395,6 +2401,7 @@ onBeforeUnmount(() => {
|
||||
calendarViewportResizeObserver = null;
|
||||
}
|
||||
clearCalendarZoomPrime();
|
||||
calendarKillTweens();
|
||||
});
|
||||
|
||||
const calendarView = ref<CalendarView>("year");
|
||||
@@ -2444,7 +2451,10 @@ type CalendarRect = { left: number; top: number; width: number; height: number }
|
||||
const calendarContentWrapRef = ref<HTMLElement | null>(null);
|
||||
const calendarContentScrollRef = ref<HTMLElement | null>(null);
|
||||
const calendarSceneRef = ref<HTMLElement | null>(null);
|
||||
const calendarFlyRectRef = ref<HTMLDivElement | null>(null);
|
||||
const calendarFlyVisible = ref(false);
|
||||
const calendarViewportHeight = ref(0);
|
||||
let calendarActiveTweens: gsap.core.Tween[] = [];
|
||||
const calendarHoveredMonthIndex = ref<number | null>(null);
|
||||
const calendarHoveredWeekStartKey = ref("");
|
||||
const calendarHoveredDayKey = ref("");
|
||||
@@ -2454,6 +2464,28 @@ function setCalendarContentWrapRef(element: HTMLElement | null) {
|
||||
calendarContentWrapRef.value = element;
|
||||
}
|
||||
|
||||
function setCalendarFlyRectRef(element: HTMLDivElement | null) {
|
||||
calendarFlyRectRef.value = element;
|
||||
}
|
||||
|
||||
function calendarTweenTo(target: gsap.TweenTarget, vars: gsap.TweenVars): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const t = gsap.to(target, {
|
||||
...vars,
|
||||
onComplete: () => {
|
||||
calendarActiveTweens = calendarActiveTweens.filter((tw) => tw !== t);
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
calendarActiveTweens.push(t);
|
||||
});
|
||||
}
|
||||
|
||||
function calendarKillTweens() {
|
||||
for (const t of calendarActiveTweens) t.kill();
|
||||
calendarActiveTweens = [];
|
||||
}
|
||||
|
||||
function setCalendarContentScrollRef(element: HTMLElement | null) {
|
||||
if (calendarViewportResizeObserver) {
|
||||
calendarViewportResizeObserver.disconnect();
|
||||
@@ -2492,20 +2524,16 @@ function onCalendarSceneMouseLeave() {
|
||||
clearCalendarZoomPrime();
|
||||
}
|
||||
const calendarZoomBusy = ref(false);
|
||||
const calendarCameraState = ref({
|
||||
active: false,
|
||||
left: 0,
|
||||
top: 0,
|
||||
scale: 1,
|
||||
durationMs: 0,
|
||||
});
|
||||
const calendarZoomPrimeToken = ref("");
|
||||
const calendarZoomPrimeScale = ref(1);
|
||||
const calendarZoomPrimeTicks = ref(0);
|
||||
let calendarWheelLockUntil = 0;
|
||||
let calendarZoomPrimeTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let calendarZoomPrimeLastAt = 0;
|
||||
const CALENDAR_ZOOM_DURATION_MS = 2400;
|
||||
const CALENDAR_ZOOM_DURATION_MS = 650;
|
||||
const CALENDAR_FLY_DURATION = 0.65;
|
||||
const CALENDAR_FADE_DURATION = 0.18;
|
||||
const CALENDAR_EASE = "power3.inOut";
|
||||
const CALENDAR_ZOOM_PRIME_STEPS = 2;
|
||||
const CALENDAR_ZOOM_PRIME_MAX_SCALE = 1.05;
|
||||
const CALENDAR_ZOOM_PRIME_RESET_MS = 900;
|
||||
@@ -2515,18 +2543,6 @@ const normalizedCalendarView = computed<CalendarHierarchyView>(() =>
|
||||
calendarView.value === "agenda" ? "month" : calendarView.value,
|
||||
);
|
||||
const calendarZoomLevelIndex = computed(() => Math.max(0, calendarZoomOrder.indexOf(normalizedCalendarView.value)));
|
||||
const calendarSceneTransformStyle = computed(() => {
|
||||
if (!calendarCameraState.value.active) return undefined;
|
||||
return {
|
||||
transform: `translate(${calendarCameraState.value.left}px, ${calendarCameraState.value.top}px) scale(${calendarCameraState.value.scale})`,
|
||||
transformOrigin: "0 0",
|
||||
transition:
|
||||
calendarCameraState.value.durationMs > 0
|
||||
? `transform ${calendarCameraState.value.durationMs}ms cubic-bezier(0.16, 0.86, 0.18, 1)`
|
||||
: "none",
|
||||
willChange: "transform",
|
||||
};
|
||||
});
|
||||
|
||||
function clearCalendarZoomPrime() {
|
||||
if (calendarZoomPrimeTimer) {
|
||||
@@ -2662,119 +2678,78 @@ function nextAnimationFrame() {
|
||||
});
|
||||
}
|
||||
|
||||
function waitForTransformTransition(element: HTMLElement) {
|
||||
return new Promise<void>((resolve) => {
|
||||
let settled = false;
|
||||
const finish = () => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
element.removeEventListener("transitionend", onTransitionEnd);
|
||||
clearTimeout(fallbackTimer);
|
||||
resolve();
|
||||
};
|
||||
const onTransitionEnd = (event: TransitionEvent) => {
|
||||
if (event.target !== element) return;
|
||||
if (event.propertyName !== "transform") return;
|
||||
finish();
|
||||
};
|
||||
const fallbackTimer = setTimeout(() => finish(), CALENDAR_ZOOM_DURATION_MS + 160);
|
||||
element.addEventListener("transitionend", onTransitionEnd);
|
||||
});
|
||||
}
|
||||
|
||||
function fadeOutCalendarSiblings(sourceElement: HTMLElement) {
|
||||
const scene = calendarSceneRef.value;
|
||||
if (!scene) return () => {};
|
||||
const targets = Array.from(scene.querySelectorAll<HTMLElement>(".calendar-hover-targetable"));
|
||||
const siblings = targets.filter((element) => {
|
||||
if (element === sourceElement) return false;
|
||||
if (sourceElement.contains(element)) return false;
|
||||
if (element.contains(sourceElement)) return false;
|
||||
return true;
|
||||
});
|
||||
const snapshots = siblings.map((element) => ({
|
||||
element,
|
||||
opacity: element.style.opacity,
|
||||
pointerEvents: element.style.pointerEvents,
|
||||
transition: element.style.transition,
|
||||
}));
|
||||
for (const { element } of snapshots) {
|
||||
element.style.transition = "opacity 180ms ease";
|
||||
element.style.opacity = "0";
|
||||
element.style.pointerEvents = "none";
|
||||
}
|
||||
return () => {
|
||||
for (const snapshot of snapshots) {
|
||||
snapshot.element.style.opacity = snapshot.opacity;
|
||||
snapshot.element.style.pointerEvents = snapshot.pointerEvents;
|
||||
snapshot.element.style.transition = snapshot.transition;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function isRenderableRect(rect: DOMRect | null) {
|
||||
return Boolean(rect && rect.width >= 2 && rect.height >= 2);
|
||||
}
|
||||
|
||||
async function animateCalendarFlipTransition(
|
||||
sourceElement: HTMLElement | null,
|
||||
_sourceElement: HTMLElement | null,
|
||||
apply: () => void,
|
||||
resolveTarget: () => HTMLElement | null,
|
||||
) {
|
||||
clearCalendarZoomPrime();
|
||||
calendarZoomBusy.value = true;
|
||||
let restoreSiblings = () => {};
|
||||
let animatedElement: HTMLElement | null = null;
|
||||
let snapshot: {
|
||||
transform: string;
|
||||
transition: string;
|
||||
transformOrigin: string;
|
||||
willChange: string;
|
||||
zIndex: string;
|
||||
} | null = null;
|
||||
calendarKillTweens();
|
||||
|
||||
const flyEl = calendarFlyRectRef.value;
|
||||
const wrapEl = calendarContentWrapRef.value;
|
||||
const sceneEl = calendarSceneRef.value;
|
||||
|
||||
if (!flyEl || !wrapEl) {
|
||||
apply();
|
||||
calendarZoomBusy.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const sourceRect = sourceElement?.getBoundingClientRect() ?? null;
|
||||
const wrapRect = wrapEl.getBoundingClientRect();
|
||||
|
||||
// 1. Fade out current content
|
||||
if (sceneEl) {
|
||||
await calendarTweenTo(sceneEl, { opacity: 0, duration: CALENDAR_FADE_DURATION, ease: "power2.in" });
|
||||
}
|
||||
|
||||
// 2. Position fly rect at full viewport
|
||||
const pad = 4;
|
||||
gsap.set(flyEl, {
|
||||
left: pad,
|
||||
top: pad,
|
||||
width: wrapRect.width - pad * 2,
|
||||
height: wrapRect.height - pad * 2,
|
||||
opacity: 1,
|
||||
borderRadius: 14,
|
||||
});
|
||||
calendarFlyVisible.value = true;
|
||||
|
||||
// 3. Switch to parent view
|
||||
apply();
|
||||
await nextTick();
|
||||
|
||||
// 4. Find target element in new view
|
||||
const targetElement = resolveTarget();
|
||||
const targetRect = targetElement?.getBoundingClientRect() ?? null;
|
||||
if (!targetElement || !isRenderableRect(sourceRect) || !isRenderableRect(targetRect)) return;
|
||||
|
||||
restoreSiblings = fadeOutCalendarSiblings(targetElement);
|
||||
animatedElement = targetElement;
|
||||
snapshot = {
|
||||
transform: targetElement.style.transform,
|
||||
transition: targetElement.style.transition,
|
||||
transformOrigin: targetElement.style.transformOrigin,
|
||||
willChange: targetElement.style.willChange,
|
||||
zIndex: targetElement.style.zIndex,
|
||||
};
|
||||
if (targetElement && targetRect && targetRect.width >= 2 && targetRect.height >= 2) {
|
||||
const tgtLeft = targetRect.left - wrapRect.left;
|
||||
const tgtTop = targetRect.top - wrapRect.top;
|
||||
|
||||
const dx = sourceRect.left - targetRect.left;
|
||||
const dy = sourceRect.top - targetRect.top;
|
||||
const sx = Math.max(0.01, sourceRect.width / targetRect.width);
|
||||
const sy = Math.max(0.01, sourceRect.height / targetRect.height);
|
||||
|
||||
targetElement.style.transformOrigin = "top left";
|
||||
targetElement.style.willChange = "transform";
|
||||
targetElement.style.zIndex = "24";
|
||||
targetElement.style.transition = "none";
|
||||
targetElement.style.transform = `translate3d(${dx}px, ${dy}px, 0px) scale(${sx}, ${sy})`;
|
||||
targetElement.getBoundingClientRect();
|
||||
await nextAnimationFrame();
|
||||
|
||||
targetElement.style.transition = `transform ${CALENDAR_ZOOM_DURATION_MS}ms cubic-bezier(0.16, 0.86, 0.18, 1)`;
|
||||
targetElement.style.transform = "translate3d(0px, 0px, 0px) scale(1, 1)";
|
||||
await waitForTransformTransition(targetElement);
|
||||
} finally {
|
||||
if (animatedElement && snapshot) {
|
||||
animatedElement.style.transform = snapshot.transform;
|
||||
animatedElement.style.transition = snapshot.transition;
|
||||
animatedElement.style.transformOrigin = snapshot.transformOrigin;
|
||||
animatedElement.style.willChange = snapshot.willChange;
|
||||
animatedElement.style.zIndex = snapshot.zIndex;
|
||||
// 5. Animate fly rect → target element
|
||||
await calendarTweenTo(flyEl, {
|
||||
left: tgtLeft,
|
||||
top: tgtTop,
|
||||
width: targetRect.width,
|
||||
height: targetRect.height,
|
||||
borderRadius: 12,
|
||||
duration: CALENDAR_FLY_DURATION,
|
||||
ease: CALENDAR_EASE,
|
||||
});
|
||||
}
|
||||
restoreSiblings();
|
||||
|
||||
// 6. Hide fly rect, fade in content
|
||||
calendarFlyVisible.value = false;
|
||||
if (sceneEl) {
|
||||
gsap.set(sceneEl, { opacity: 0 });
|
||||
await calendarTweenTo(sceneEl, { opacity: 1, duration: 0.25, ease: "power2.out" });
|
||||
}
|
||||
} finally {
|
||||
calendarFlyVisible.value = false;
|
||||
calendarZoomBusy.value = false;
|
||||
}
|
||||
}
|
||||
@@ -2785,60 +2760,75 @@ async function animateCalendarZoomIntoSource(
|
||||
) {
|
||||
clearCalendarZoomPrime();
|
||||
calendarZoomBusy.value = true;
|
||||
let restoreSiblings = () => {};
|
||||
let snapshot: {
|
||||
transform: string;
|
||||
transition: string;
|
||||
transformOrigin: string;
|
||||
willChange: string;
|
||||
zIndex: string;
|
||||
} | null = null;
|
||||
calendarKillTweens();
|
||||
|
||||
const flyEl = calendarFlyRectRef.value;
|
||||
const wrapEl = calendarContentWrapRef.value;
|
||||
const scrollEl = calendarContentScrollRef.value;
|
||||
const sceneEl = calendarSceneRef.value;
|
||||
|
||||
if (!sourceElement || !flyEl || !wrapEl || !scrollEl) {
|
||||
apply();
|
||||
calendarZoomBusy.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const viewportRect = calendarContentScrollRef.value?.getBoundingClientRect() ?? null;
|
||||
const sourceRect = sourceElement?.getBoundingClientRect() ?? null;
|
||||
if (!sourceElement || !isRenderableRect(viewportRect) || !isRenderableRect(sourceRect)) {
|
||||
const wrapRect = wrapEl.getBoundingClientRect();
|
||||
const sourceRect = sourceElement.getBoundingClientRect();
|
||||
if (sourceRect.width < 2 || sourceRect.height < 2) {
|
||||
apply();
|
||||
return;
|
||||
}
|
||||
|
||||
restoreSiblings = fadeOutCalendarSiblings(sourceElement);
|
||||
snapshot = {
|
||||
transform: sourceElement.style.transform,
|
||||
transition: sourceElement.style.transition,
|
||||
transformOrigin: sourceElement.style.transformOrigin,
|
||||
willChange: sourceElement.style.willChange,
|
||||
zIndex: sourceElement.style.zIndex,
|
||||
};
|
||||
// 1. Fade out siblings
|
||||
const siblings = Array.from(
|
||||
sceneEl?.querySelectorAll<HTMLElement>(".calendar-hover-targetable") ?? [],
|
||||
).filter((el) => el !== sourceElement && !sourceElement.contains(el) && !el.contains(sourceElement));
|
||||
await calendarTweenTo(siblings, { opacity: 0, duration: CALENDAR_FADE_DURATION, ease: "power2.in" });
|
||||
|
||||
const dx = viewportRect.left - sourceRect.left;
|
||||
const dy = viewportRect.top - sourceRect.top;
|
||||
const sx = Math.max(0.01, viewportRect.width / sourceRect.width);
|
||||
const sy = Math.max(0.01, viewportRect.height / sourceRect.height);
|
||||
// 2. Position fly rect at source element
|
||||
const srcLeft = sourceRect.left - wrapRect.left;
|
||||
const srcTop = sourceRect.top - wrapRect.top;
|
||||
gsap.set(flyEl, {
|
||||
left: srcLeft,
|
||||
top: srcTop,
|
||||
width: sourceRect.width,
|
||||
height: sourceRect.height,
|
||||
opacity: 1,
|
||||
borderRadius: 12,
|
||||
});
|
||||
calendarFlyVisible.value = true;
|
||||
|
||||
sourceElement.style.transformOrigin = "top left";
|
||||
sourceElement.style.willChange = "transform";
|
||||
sourceElement.style.zIndex = "24";
|
||||
sourceElement.style.transition = "none";
|
||||
sourceElement.style.transform = "translate3d(0px, 0px, 0px) scale(1, 1)";
|
||||
sourceElement.getBoundingClientRect();
|
||||
await nextAnimationFrame();
|
||||
|
||||
sourceElement.style.transition = `transform ${CALENDAR_ZOOM_DURATION_MS}ms cubic-bezier(0.16, 0.86, 0.18, 1)`;
|
||||
sourceElement.style.transform = `translate3d(${dx}px, ${dy}px, 0px) scale(${sx}, ${sy})`;
|
||||
await waitForTransformTransition(sourceElement);
|
||||
// 3. Animate fly rect → full viewport
|
||||
const pad = 4;
|
||||
await calendarTweenTo(flyEl, {
|
||||
left: pad,
|
||||
top: pad,
|
||||
width: wrapRect.width - pad * 2,
|
||||
height: wrapRect.height - pad * 2,
|
||||
borderRadius: 14,
|
||||
duration: CALENDAR_FLY_DURATION,
|
||||
ease: CALENDAR_EASE,
|
||||
});
|
||||
|
||||
// 4. Switch content
|
||||
apply();
|
||||
await nextTick();
|
||||
await nextAnimationFrame();
|
||||
} finally {
|
||||
if (sourceElement && snapshot) {
|
||||
sourceElement.style.transform = snapshot.transform;
|
||||
sourceElement.style.transition = snapshot.transition;
|
||||
sourceElement.style.transformOrigin = snapshot.transformOrigin;
|
||||
sourceElement.style.willChange = snapshot.willChange;
|
||||
sourceElement.style.zIndex = snapshot.zIndex;
|
||||
|
||||
// 5. Hide fly rect, fade in new content
|
||||
calendarFlyVisible.value = false;
|
||||
if (sceneEl) {
|
||||
gsap.set(sceneEl, { opacity: 0 });
|
||||
await calendarTweenTo(sceneEl, { opacity: 1, duration: 0.25, ease: "power2.out" });
|
||||
}
|
||||
restoreSiblings();
|
||||
|
||||
// 6. Restore sibling opacity
|
||||
for (const el of siblings) {
|
||||
el.style.opacity = "";
|
||||
}
|
||||
} finally {
|
||||
calendarFlyVisible.value = false;
|
||||
calendarZoomBusy.value = false;
|
||||
}
|
||||
}
|
||||
@@ -4876,7 +4866,8 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
|
||||
:set-calendar-scene-ref="setCalendarSceneRef"
|
||||
:calendar-viewport-height="calendarViewportHeight"
|
||||
:normalized-calendar-view="normalizedCalendarView"
|
||||
:calendar-scene-transform-style="calendarSceneTransformStyle"
|
||||
:calendar-fly-visible="calendarFlyVisible"
|
||||
:set-calendar-fly-rect-ref="setCalendarFlyRectRef"
|
||||
:on-calendar-scene-mouse-leave="onCalendarSceneMouseLeave"
|
||||
:calendar-view="calendarView"
|
||||
:year-months="yearMonths"
|
||||
|
||||
@@ -54,7 +54,6 @@ defineProps<{
|
||||
setCalendarSceneRef: (element: HTMLDivElement | null) => void;
|
||||
calendarViewportHeight: number;
|
||||
normalizedCalendarView: string;
|
||||
calendarSceneTransformStyle: Record<string, string>;
|
||||
onCalendarSceneMouseLeave: () => void;
|
||||
calendarView: string;
|
||||
yearMonths: YearMonthItem[];
|
||||
@@ -80,6 +79,8 @@ defineProps<{
|
||||
weekDays: WeekDay[];
|
||||
calendarPrimeDayToken: (dayKey: string) => string;
|
||||
selectedDayEvents: CalendarEvent[];
|
||||
calendarFlyVisible: boolean;
|
||||
setCalendarFlyRectRef: (element: HTMLDivElement | null) => void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -156,6 +157,14 @@ defineProps<{
|
||||
>
|
||||
<span>→</span>
|
||||
</button>
|
||||
|
||||
<!-- GSAP flying rect (zoom transition overlay) -->
|
||||
<div
|
||||
v-show="calendarFlyVisible"
|
||||
:ref="setCalendarFlyRectRef"
|
||||
class="calendar-fly-rect"
|
||||
/>
|
||||
|
||||
<div
|
||||
:ref="setCalendarContentScrollRef"
|
||||
class="calendar-content-scroll min-h-0 h-full overflow-y-auto pr-1"
|
||||
@@ -167,7 +176,6 @@ defineProps<{
|
||||
'calendar-scene',
|
||||
normalizedCalendarView === 'day' ? 'cursor-zoom-out' : 'cursor-zoom-in',
|
||||
]"
|
||||
:style="calendarSceneTransformStyle"
|
||||
@mouseleave="onCalendarSceneMouseLeave"
|
||||
>
|
||||
<div
|
||||
@@ -441,6 +449,16 @@ defineProps<{
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.calendar-fly-rect {
|
||||
position: absolute;
|
||||
border-radius: 12px;
|
||||
border: 2px solid color-mix(in oklab, var(--color-primary) 70%, transparent);
|
||||
background: color-mix(in oklab, var(--color-base-200) 60%, transparent);
|
||||
z-index: 20;
|
||||
pointer-events: none;
|
||||
will-change: left, top, width, height;
|
||||
}
|
||||
|
||||
.calendar-zoom-inline {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
@@ -23,6 +23,11 @@ export type ArchiveCalendarEventInput = {
|
||||
id: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
export type CalendarDateRange = {
|
||||
from?: InputMaybe<Scalars['String']['input']>;
|
||||
to?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type CalendarEvent = {
|
||||
__typename?: 'CalendarEvent';
|
||||
archiveNote: Scalars['String']['output'];
|
||||
@@ -387,6 +392,11 @@ export type Query = {
|
||||
};
|
||||
|
||||
|
||||
export type QuerycalendarArgs = {
|
||||
dateRange?: InputMaybe<CalendarDateRange>;
|
||||
};
|
||||
|
||||
|
||||
export type QuerygetClientTimelineArgs = {
|
||||
contactId: Scalars['ID']['input'];
|
||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||
@@ -418,7 +428,10 @@ export type ArchiveChatConversationMutationMutationVariables = Exact<{
|
||||
|
||||
export type ArchiveChatConversationMutationMutation = { __typename?: 'Mutation', archiveChatConversation: { __typename?: 'MutationResult', ok: boolean } };
|
||||
|
||||
export type CalendarQueryQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
export type CalendarQueryQueryVariables = Exact<{
|
||||
from?: InputMaybe<Scalars['String']['input']>;
|
||||
to?: InputMaybe<Scalars['String']['input']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type CalendarQueryQuery = { __typename?: 'Query', calendar: Array<{ __typename?: 'CalendarEvent', id: string, title: string, start: string, end: string, contact: string, note: string, isArchived: boolean, createdAt: string, archiveNote: string, archivedAt: string }> };
|
||||
@@ -670,8 +683,8 @@ export function useArchiveChatConversationMutationMutation(options: VueApolloCom
|
||||
}
|
||||
export type ArchiveChatConversationMutationMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<ArchiveChatConversationMutationMutation, ArchiveChatConversationMutationMutationVariables>;
|
||||
export const CalendarQueryDocument = gql`
|
||||
query CalendarQuery {
|
||||
calendar {
|
||||
query CalendarQuery($from: String, $to: String) {
|
||||
calendar(dateRange: {from: $from, to: $to}) {
|
||||
id
|
||||
title
|
||||
start
|
||||
@@ -693,16 +706,20 @@ export const CalendarQueryDocument = gql`
|
||||
* When your component renders, `useCalendarQueryQuery` returns an object from Apollo Client that contains result, loading and error properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param variables that will be passed into the query
|
||||
* @param options that will be passed into the query, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/query.html#options;
|
||||
*
|
||||
* @example
|
||||
* const { result, loading, error } = useCalendarQueryQuery();
|
||||
* const { result, loading, error } = useCalendarQueryQuery({
|
||||
* from: // value for 'from'
|
||||
* to: // value for 'to'
|
||||
* });
|
||||
*/
|
||||
export function useCalendarQueryQuery(options: VueApolloComposable.UseQueryOptions<CalendarQueryQuery, CalendarQueryQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<CalendarQueryQuery, CalendarQueryQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<CalendarQueryQuery, CalendarQueryQueryVariables>> = {}) {
|
||||
return VueApolloComposable.useQuery<CalendarQueryQuery, CalendarQueryQueryVariables>(CalendarQueryDocument, {}, options);
|
||||
export function useCalendarQueryQuery(variables: CalendarQueryQueryVariables | VueCompositionApi.Ref<CalendarQueryQueryVariables> | ReactiveFunction<CalendarQueryQueryVariables> = {}, options: VueApolloComposable.UseQueryOptions<CalendarQueryQuery, CalendarQueryQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<CalendarQueryQuery, CalendarQueryQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<CalendarQueryQuery, CalendarQueryQueryVariables>> = {}) {
|
||||
return VueApolloComposable.useQuery<CalendarQueryQuery, CalendarQueryQueryVariables>(CalendarQueryDocument, variables, options);
|
||||
}
|
||||
export function useCalendarQueryLazyQuery(options: VueApolloComposable.UseQueryOptions<CalendarQueryQuery, CalendarQueryQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<CalendarQueryQuery, CalendarQueryQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<CalendarQueryQuery, CalendarQueryQueryVariables>> = {}) {
|
||||
return VueApolloComposable.useLazyQuery<CalendarQueryQuery, CalendarQueryQueryVariables>(CalendarQueryDocument, {}, options);
|
||||
export function useCalendarQueryLazyQuery(variables: CalendarQueryQueryVariables | VueCompositionApi.Ref<CalendarQueryQueryVariables> | ReactiveFunction<CalendarQueryQueryVariables> = {}, options: VueApolloComposable.UseQueryOptions<CalendarQueryQuery, CalendarQueryQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<CalendarQueryQuery, CalendarQueryQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<CalendarQueryQuery, CalendarQueryQueryVariables>> = {}) {
|
||||
return VueApolloComposable.useLazyQuery<CalendarQueryQuery, CalendarQueryQueryVariables>(CalendarQueryDocument, variables, options);
|
||||
}
|
||||
export type CalendarQueryQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<CalendarQueryQuery, CalendarQueryQueryVariables>;
|
||||
export const ChatConversationsQueryDocument = gql`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
query CalendarQuery {
|
||||
calendar {
|
||||
query CalendarQuery($from: String, $to: String) {
|
||||
calendar(dateRange: { from: $from, to: $to }) {
|
||||
id
|
||||
title
|
||||
start
|
||||
|
||||
@@ -5,7 +5,7 @@ type Query {
|
||||
contacts: [Contact!]!
|
||||
communications: [CommItem!]!
|
||||
contactInboxes: [ContactInbox!]!
|
||||
calendar: [CalendarEvent!]!
|
||||
calendar(dateRange: CalendarDateRange): [CalendarEvent!]!
|
||||
deals: [Deal!]!
|
||||
feed: [FeedCard!]!
|
||||
pins: [CommPin!]!
|
||||
@@ -49,6 +49,11 @@ type PinToggleResult {
|
||||
pinned: Boolean!
|
||||
}
|
||||
|
||||
input CalendarDateRange {
|
||||
from: String
|
||||
to: String
|
||||
}
|
||||
|
||||
input CreateCalendarEventInput {
|
||||
title: String!
|
||||
start: String!
|
||||
|
||||
@@ -675,10 +675,10 @@ async function getContactInboxes(auth: AuthContext | null) {
|
||||
}));
|
||||
}
|
||||
|
||||
async function getCalendar(auth: AuthContext | null) {
|
||||
async function getCalendar(auth: AuthContext | null, dateRange?: { from?: string; to?: string }) {
|
||||
const ctx = requireAuth(auth);
|
||||
const from = new Date(Date.now() - 1000 * 60 * 60 * 24 * 30);
|
||||
const to = new Date(Date.now() + 1000 * 60 * 60 * 24 * 60);
|
||||
const from = dateRange?.from ? new Date(dateRange.from) : new Date(Date.now() - 1000 * 60 * 60 * 24 * 30);
|
||||
const to = dateRange?.to ? new Date(dateRange.to) : new Date(Date.now() + 1000 * 60 * 60 * 24 * 60);
|
||||
|
||||
const calendarRaw = await prisma.calendarEvent.findMany({
|
||||
where: { teamId: ctx.teamId, startsAt: { gte: from, lte: to } },
|
||||
@@ -1842,7 +1842,7 @@ export const crmGraphqlSchema = buildSchema(`
|
||||
contacts: [Contact!]!
|
||||
communications: [CommItem!]!
|
||||
contactInboxes: [ContactInbox!]!
|
||||
calendar: [CalendarEvent!]!
|
||||
calendar(dateRange: CalendarDateRange): [CalendarEvent!]!
|
||||
deals: [Deal!]!
|
||||
feed: [FeedCard!]!
|
||||
pins: [CommPin!]!
|
||||
@@ -1886,6 +1886,11 @@ export const crmGraphqlSchema = buildSchema(`
|
||||
pinned: Boolean!
|
||||
}
|
||||
|
||||
input CalendarDateRange {
|
||||
from: String
|
||||
to: String
|
||||
}
|
||||
|
||||
input CreateCalendarEventInput {
|
||||
title: String!
|
||||
start: String!
|
||||
@@ -2113,7 +2118,7 @@ export const crmGraphqlRoot = {
|
||||
contacts: async (_args: unknown, context: GraphQLContext) => getContacts(context.auth),
|
||||
communications: async (_args: unknown, context: GraphQLContext) => getCommunications(context.auth),
|
||||
contactInboxes: async (_args: unknown, context: GraphQLContext) => getContactInboxes(context.auth),
|
||||
calendar: async (_args: unknown, context: GraphQLContext) => getCalendar(context.auth),
|
||||
calendar: async (args: { dateRange?: { from?: string; to?: string } }, context: GraphQLContext) => getCalendar(context.auth, args.dateRange ?? undefined),
|
||||
deals: async (_args: unknown, context: GraphQLContext) => getDeals(context.auth),
|
||||
feed: async (_args: unknown, context: GraphQLContext) => getFeed(context.auth),
|
||||
pins: async (_args: unknown, context: GraphQLContext) => getPins(context.auth),
|
||||
|
||||
Reference in New Issue
Block a user