Skip to content

Commit

Permalink
feat(timezone-date): add composable for handling timezone-aware dates…
Browse files Browse the repository at this point in the history
… & add ProjectLinkButton (#5191)

* feat: add ProjectLinkButton component for project navigation

Signed-off-by: Wanjin Noh <[email protected]>

* feat(timezone-date): add composable for handling timezone-aware dates

Signed-off-by: Wanjin Noh <[email protected]>

* test: add unit tests for useTimezoneDate composable functionality

Signed-off-by: Wanjin Noh <[email protected]>

---------

Signed-off-by: Wanjin Noh <[email protected]>
  • Loading branch information
WANZARGEN authored Dec 10, 2024
1 parent 2b8d701 commit 78cf1e8
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ref } from 'vue';

import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
import {
describe, it, expect, vi,
} from 'vitest';

import { useTimezoneDate } from '@/common/composables/timezone-date';

// Configure dayjs plugins
dayjs.extend(utc);
dayjs.extend(timezone);

// Mock userStore
vi.mock('@/store/user/user-store', () => ({
useUserStore: () => ({
state: {
timezone: 'Asia/Seoul',
},
}),
}));

describe('useTimezoneDate', () => {
it('should return the correct timezone date when date is provided', () => {
const dateRef = ref('2024-12-09T00:00:00Z');
const { timezoneDate, getTimezoneDate } = useTimezoneDate({ date: dateRef });

// Validate: getTimezoneDate function
const expectedDate = dayjs.utc(dateRef.value).tz('Asia/Seoul').format('YYYY/MM/DD HH:mm:ss');
expect(getTimezoneDate(dateRef.value)).toBe(expectedDate);

// Validate: computed timezoneDate
expect(timezoneDate?.value).toBe(expectedDate);
});

it('should return undefined for timezoneDate when date is not provided', () => {
const { timezoneDate, getTimezoneDate } = useTimezoneDate();

// Validate: timezoneDate should be undefined
expect(timezoneDate).toBeUndefined();

// Validate: getTimezoneDate should still work
const inputDate = '2024-12-09T00:00:00Z';
const expectedDate = dayjs.utc(inputDate).tz('Asia/Seoul').format('YYYY/MM/DD HH:mm:ss');
expect(getTimezoneDate(inputDate)).toBe(expectedDate);
});

it('should update timezoneDate when date value changes', async () => {
const dateRef = ref('2024-12-09T00:00:00Z');
const { timezoneDate } = useTimezoneDate({ date: dateRef });

// Validate initial value
const initialExpectedDate = dayjs.utc(dateRef.value).tz('Asia/Seoul').format('YYYY/MM/DD HH:mm:ss');
expect(timezoneDate?.value).toBe(initialExpectedDate);

// Change the date value
dateRef.value = '2024-12-10T12:00:00Z';

// Validate updated value
const updatedExpectedDate = dayjs.utc(dateRef.value).tz('Asia/Seoul').format('YYYY/MM/DD HH:mm:ss');
expect(timezoneDate?.value).toBe(updatedExpectedDate);
});
});
21 changes: 21 additions & 0 deletions apps/web/src/common/composables/timezone-date/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Ref } from 'vue';
import { computed } from 'vue';

import dayjs from 'dayjs';

import { useUserStore } from '@/store/user/user-store';

interface UseTimezoneDateOptions {
date?: Ref<string>;
}
export const useTimezoneDate = ({
date,
}: UseTimezoneDateOptions = {}) => {
const userStore = useUserStore();
const getTimezoneDate = (_date: string): string => dayjs.utc(_date).tz(userStore.state.timezone ?? 'UTC').format('YYYY/MM/DD HH:mm:ss');
const timezoneDate = date ? computed(() => getTimezoneDate(date.value)) : undefined;
return {
getTimezoneDate,
timezoneDate,
};
};
56 changes: 56 additions & 0 deletions apps/web/src/common/modules/project/ProjectLinkButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script setup lang="ts">
import { computed } from 'vue';
import type { Location } from 'vue-router';
import { PTextButton, PI, PLink } from '@cloudforet/mirinae';
import { useProjectReferenceStore } from '@/store/reference/project-reference-store';
import { useProperRouteLocation } from '@/common/composables/proper-route-location';
import { PROJECT_ROUTE } from '@/services/project/routes/route-constant';
const props = defineProps<{
projectId: string;
to?: Location;
useClickEvent?: boolean;
}>();
const emit = defineEmits<{(event: 'click'): void;
}>();
const projectReferenceStore = useProjectReferenceStore();
const { getProperRouteLocation } = useProperRouteLocation();
const projectPageLocation = computed<Location>(() => (getProperRouteLocation({
name: PROJECT_ROUTE.DETAIL._NAME,
params: {
id: props.projectId,
},
})));
</script>

<template>
<span>
<p-link v-if="!props.useClickEvent"
action-icon="internal-link"
new-tab
:to="props.to || projectPageLocation"
>
{{ projectReferenceStore.getters.projectItems[props.projectId]?.label || props.projectId }}
</p-link>
<p-text-button v-else
class="text-gray-900"
size="md"
@click="emit('click')"
>
<span>
{{ projectReferenceStore.getters.projectItems[props.projectId]?.label || props.projectId }}
<p-i name="ic_arrow-right-up"
class="link-mark"
height="0.875rem"
width="0.875rem"
color="inherit"
/>
</span>
</p-text-button>
</span>
</template>

0 comments on commit 78cf1e8

Please sign in to comment.