This commit is contained in:
commit
33453ecc55
3475 files changed
+850363
No files matched your search
@@ -0,0 +1,369 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
import type { ChannelInfo } from '@/entities/channel';
|
||||
import type { FieldInfo } from '@/entities/field';
|
||||
|
||||
import type { CreateChannelStepKey } from './create-channel-type';
|
||||
import {
|
||||
CREATE_CHANNEL_MAIN_STEP_LIST,
|
||||
CREATE_CHANNEL_STEP_MAP,
|
||||
FIRST_CREATE_CHANNEL_STEP,
|
||||
LAST_CREATE_CHANNEL_STEP,
|
||||
} from './create-channel-type';
|
||||
|
||||
const DEFAULT_FIELDS: FieldInfo[] = [
|
||||
{
|
||||
format: 'number',
|
||||
property: 'READ_ONLY',
|
||||
status: 'ACTIVE',
|
||||
name: 'ID',
|
||||
key: 'id',
|
||||
description: '',
|
||||
order: 0,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'date',
|
||||
property: 'READ_ONLY',
|
||||
status: 'ACTIVE',
|
||||
name: 'Created',
|
||||
key: 'createdAt',
|
||||
description: '',
|
||||
order: 1,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'date',
|
||||
property: 'READ_ONLY',
|
||||
status: 'ACTIVE',
|
||||
name: 'Updated',
|
||||
key: 'updatedAt',
|
||||
description: '',
|
||||
order: 2,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'multiSelect',
|
||||
property: 'EDITABLE',
|
||||
status: 'ACTIVE',
|
||||
name: 'Issue',
|
||||
key: 'issues',
|
||||
description: '',
|
||||
options: [],
|
||||
order: 3,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'text',
|
||||
property: 'EDITABLE',
|
||||
status: 'ACTIVE',
|
||||
name: 'title',
|
||||
key: 'title',
|
||||
description: '제목',
|
||||
order: 4,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'text',
|
||||
property: 'EDITABLE',
|
||||
status: 'ACTIVE',
|
||||
name: 'contents',
|
||||
key: 'contents',
|
||||
description: '내용',
|
||||
order: 5,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'select',
|
||||
property: 'EDITABLE',
|
||||
status: 'ACTIVE',
|
||||
name: '중요도',
|
||||
key: 'priority',
|
||||
description: '관리자가 지정하는 피드백 중요도입니다.',
|
||||
options: [
|
||||
{ key: 'LOW', name: '낮음' },
|
||||
{ key: 'MEDIUM', name: '보통' },
|
||||
{ key: 'HIGH', name: '높음' },
|
||||
{ key: 'CRITICAL', name: '긴급' },
|
||||
],
|
||||
order: 6,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'text',
|
||||
property: 'EDITABLE',
|
||||
status: 'ACTIVE',
|
||||
name: 'IP',
|
||||
key: 'IP',
|
||||
description: '사용자 ip주소',
|
||||
order: 7,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'text',
|
||||
property: 'EDITABLE',
|
||||
status: 'ACTIVE',
|
||||
name: 'MAC_address',
|
||||
key: 'MAC_address',
|
||||
description: '사용자 MAC주소',
|
||||
order: 8,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'select',
|
||||
property: 'EDITABLE',
|
||||
status: 'ACTIVE',
|
||||
name: 'Category',
|
||||
key: 'Category',
|
||||
description: '문의 구분',
|
||||
options: [
|
||||
{ key: 'GENERAL_QNA', name: '일반문의' },
|
||||
{ key: 'ERROR_QNA', name: '오류문의' },
|
||||
{ key: 'IMPROVEMENT_QNA', name: '개선문의' },
|
||||
],
|
||||
order: 9,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'text',
|
||||
property: 'READ_ONLY',
|
||||
status: 'ACTIVE',
|
||||
name: '작성자',
|
||||
key: 'requester_id',
|
||||
description: '',
|
||||
order: 10,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'text',
|
||||
property: 'READ_ONLY',
|
||||
status: 'ACTIVE',
|
||||
name: '테넌트ID',
|
||||
key: 'requester_tenant_id',
|
||||
description: '',
|
||||
order: 11,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'text',
|
||||
property: 'READ_ONLY',
|
||||
status: 'ACTIVE',
|
||||
name: 'e-mail',
|
||||
key: 'requester_email',
|
||||
description: '',
|
||||
order: 12,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'text',
|
||||
property: 'READ_ONLY',
|
||||
status: 'ACTIVE',
|
||||
name: '이름',
|
||||
key: 'requester_name',
|
||||
description: '',
|
||||
order: 13,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'text',
|
||||
property: 'READ_ONLY',
|
||||
status: 'ACTIVE',
|
||||
name: '부서',
|
||||
key: 'requester_department',
|
||||
description: '',
|
||||
order: 14,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'text',
|
||||
property: 'READ_ONLY',
|
||||
status: 'ACTIVE',
|
||||
name: 'is_secret',
|
||||
key: 'is_secret',
|
||||
description: '',
|
||||
order: 15,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'select',
|
||||
property: 'EDITABLE',
|
||||
status: 'ACTIVE',
|
||||
name: '피드백 상태',
|
||||
key: 'feedback_status',
|
||||
description: '피드백 상태',
|
||||
options: [
|
||||
{ key: 'INIT', name: '신규' },
|
||||
{ key: 'ON_REVIEW', name: '검토중' },
|
||||
{ key: 'DETAILED_REVIEW', name: '정밀검토중' },
|
||||
{ key: 'IN_PROGRESS', name: '진행중' },
|
||||
{ key: 'RESOLVED', name: '완료' },
|
||||
{ key: 'PENDING', name: '진행하지 않음' },
|
||||
],
|
||||
order: 16,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
{
|
||||
format: 'text',
|
||||
property: 'READ_ONLY',
|
||||
status: 'ACTIVE',
|
||||
name: '담당자',
|
||||
key: 'assignee',
|
||||
description: '관리자가 지정한 피드백 담당자입니다.',
|
||||
order: 17,
|
||||
aiFieldTargetKeys: null,
|
||||
aiFieldTemplateId: null,
|
||||
aiFieldAutoProcessing: null,
|
||||
},
|
||||
];
|
||||
|
||||
interface Input {
|
||||
channelInfo: ChannelInfo;
|
||||
fields: FieldInfo[];
|
||||
fieldPreview: null;
|
||||
}
|
||||
|
||||
interface Step {
|
||||
key: CreateChannelStepKey;
|
||||
index: number;
|
||||
}
|
||||
|
||||
interface State {
|
||||
editingStepIndex: number | null;
|
||||
currentStep: Step;
|
||||
input: Input;
|
||||
}
|
||||
|
||||
interface Action {
|
||||
jumpStepByIndex: (index: number) => void;
|
||||
jumpStepByKey: (key: CreateChannelStepKey) => void;
|
||||
prevStep: () => void;
|
||||
nextStep: () => void;
|
||||
reset: () => void;
|
||||
onChangeInput: <T extends keyof Input>(key: T, value: Input[T]) => void;
|
||||
setEditingStepIndex: (index: number) => void;
|
||||
}
|
||||
export const CREATE_CHANNEL_DEFAULT_INPUT: Input = {
|
||||
channelInfo: { name: '', description: '', feedbackSearchMaxDays: 365 },
|
||||
fields: DEFAULT_FIELDS,
|
||||
fieldPreview: null,
|
||||
};
|
||||
|
||||
const DEFAULT_STATE: State = {
|
||||
editingStepIndex: null,
|
||||
currentStep: {
|
||||
key: 'channel-info',
|
||||
index: 0,
|
||||
},
|
||||
input: {
|
||||
channelInfo: { name: '', description: '', feedbackSearchMaxDays: 365 },
|
||||
fields: DEFAULT_FIELDS,
|
||||
fieldPreview: null,
|
||||
},
|
||||
};
|
||||
|
||||
export const useCreateChannelStore = create<State & Action>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
...DEFAULT_STATE,
|
||||
setEditingStepIndex(index) {
|
||||
set({ editingStepIndex: index });
|
||||
},
|
||||
onChangeInput: <T extends keyof Input>(key: T, value: Input[T]) => {
|
||||
set(({ input }) => ({ input: { ...input, [key]: value } }));
|
||||
},
|
||||
jumpStepByIndex(index) {
|
||||
set({
|
||||
currentStep: {
|
||||
index,
|
||||
key: CREATE_CHANNEL_MAIN_STEP_LIST[index] ?? 'channel-info',
|
||||
},
|
||||
});
|
||||
},
|
||||
jumpStepByKey(key) {
|
||||
set({ currentStep: { index: CREATE_CHANNEL_STEP_MAP[key], key } });
|
||||
},
|
||||
nextStep() {
|
||||
set(({ currentStep }) => ({
|
||||
currentStep: {
|
||||
index: Math.min(currentStep.index + 1, LAST_CREATE_CHANNEL_STEP),
|
||||
key:
|
||||
CREATE_CHANNEL_MAIN_STEP_LIST[currentStep.index + 1] ??
|
||||
'channel-info',
|
||||
},
|
||||
editingStepIndex: Math.min(
|
||||
currentStep.index + 1,
|
||||
LAST_CREATE_CHANNEL_STEP,
|
||||
),
|
||||
}));
|
||||
},
|
||||
prevStep() {
|
||||
set(({ currentStep }) => ({
|
||||
currentStep: {
|
||||
index: Math.max(currentStep.index - 1, FIRST_CREATE_CHANNEL_STEP),
|
||||
key:
|
||||
CREATE_CHANNEL_MAIN_STEP_LIST[currentStep.index - 1] ??
|
||||
'channel-info',
|
||||
},
|
||||
}));
|
||||
},
|
||||
reset() {
|
||||
set({ ...DEFAULT_STATE });
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'create-channel',
|
||||
version: 5,
|
||||
migrate: () => DEFAULT_STATE,
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export const CREATE_CHANNEL_STEP_KEY_LIST = [
|
||||
'channel-info',
|
||||
'field',
|
||||
'field-preview',
|
||||
] as const;
|
||||
|
||||
export const CREATE_CHANNEL_MAIN_STEP_LIST: CreateChannelStepKey[] = [
|
||||
'channel-info',
|
||||
'field',
|
||||
];
|
||||
export const CREATE_CHANNEL_STEP_MAP: Record<CreateChannelStepKey, number> = {
|
||||
'channel-info': 0,
|
||||
field: 1,
|
||||
'field-preview': 1,
|
||||
};
|
||||
|
||||
export const LAST_CREATE_CHANNEL_STEP = CREATE_CHANNEL_STEP_KEY_LIST.length;
|
||||
export const FIRST_CREATE_CHANNEL_STEP = 0;
|
||||
|
||||
export type CreateChannelStepKey =
|
||||
(typeof CREATE_CHANNEL_STEP_KEY_LIST)[number];
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Trans } from 'next-i18next';
|
||||
|
||||
import type { CreateChannelStepKey } from './create-channel-type';
|
||||
import InputChannelInfoStep from './ui/input-channel-info-step.ui';
|
||||
import InputFieldPreviewStep from './ui/input-field-preview-step.ui';
|
||||
import InputFieldStep from './ui/input-field-step.ui';
|
||||
|
||||
export const CREATE_CHANNEL_COMPONENTS: Record<
|
||||
CreateChannelStepKey,
|
||||
React.ReactNode
|
||||
> = {
|
||||
'channel-info': <InputChannelInfoStep />,
|
||||
field: <InputFieldStep />,
|
||||
'field-preview': <InputFieldPreviewStep />,
|
||||
};
|
||||
|
||||
export const CREATE_CHANNEL_STEPPER_TEXT: Record<
|
||||
CreateChannelStepKey,
|
||||
React.ReactNode
|
||||
> = {
|
||||
'channel-info': <Trans i18nKey="channel-setting-menu.channel-info" />,
|
||||
field: <Trans i18nKey="channel-setting-menu.field-mgmt" />,
|
||||
'field-preview': <Trans i18nKey="main.setting.field-mgmt.preview" />,
|
||||
};
|
||||
|
||||
export const CREATE_CHANNEL_HELP_TEXT: Record<
|
||||
CreateChannelStepKey,
|
||||
React.ReactNode
|
||||
> = {
|
||||
'channel-info': <Trans i18nKey="help-card.channel-info" />,
|
||||
field: <Trans i18nKey="help-card.field" />,
|
||||
'field-preview': <Trans i18nKey="help-card.field-preview" />,
|
||||
};
|
||||
export const CREATE_CHANNEL_REQUIRED_STEP: Record<
|
||||
CreateChannelStepKey,
|
||||
boolean
|
||||
> = {
|
||||
'channel-info': true,
|
||||
field: false,
|
||||
'field-preview': false,
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export * from './ui';
|
||||
export * from './create-channel-model';
|
||||
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useMemo } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useOverlay } from '@toss/use-overlay';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
toast,
|
||||
} from '@ufb/react';
|
||||
import { ErrorCode } from '@ufb/shared';
|
||||
|
||||
import {
|
||||
CreateInputTemplate,
|
||||
isObjectEqual,
|
||||
Path,
|
||||
useAllChannels,
|
||||
useOAIMutation,
|
||||
useWarnIfSavedChanges,
|
||||
} from '@/shared';
|
||||
import { isDefaultField } from '@/entities/field';
|
||||
|
||||
import {
|
||||
CREATE_CHANNEL_DEFAULT_INPUT,
|
||||
useCreateChannelStore,
|
||||
} from '../create-channel-model';
|
||||
import { CREATE_CHANNEL_MAIN_STEP_LIST } from '../create-channel-type';
|
||||
import {
|
||||
CREATE_CHANNEL_HELP_TEXT,
|
||||
CREATE_CHANNEL_REQUIRED_STEP,
|
||||
CREATE_CHANNEL_STEPPER_TEXT,
|
||||
} from '../create-channel.constant';
|
||||
|
||||
interface IProps extends React.PropsWithChildren {
|
||||
actionButton?: React.ReactNode;
|
||||
validate?: () => Promise<boolean> | boolean;
|
||||
disableNextBtn?: boolean;
|
||||
isLoading?: boolean;
|
||||
onClickBack?: () => void;
|
||||
}
|
||||
|
||||
const CreateChannelInputTemplate: React.FC<IProps> = (props) => {
|
||||
const {
|
||||
children,
|
||||
actionButton,
|
||||
validate,
|
||||
disableNextBtn,
|
||||
isLoading,
|
||||
onClickBack,
|
||||
} = props;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { currentStep, nextStep, prevStep, input, reset, jumpStepByKey } =
|
||||
useCreateChannelStore();
|
||||
|
||||
const router = useRouter();
|
||||
const projectId = Number(router.query.projectId);
|
||||
const overlay = useOverlay();
|
||||
const { refetch } = useAllChannels(projectId);
|
||||
|
||||
const openCreateChannelError = () => {
|
||||
overlay.open(({ isOpen, close }) => (
|
||||
<Dialog open={isOpen} onOpenChange={close}>
|
||||
<DialogContent>
|
||||
<DialogHeader>{t('text.guide')}</DialogHeader>
|
||||
<DialogBody>
|
||||
<DialogDescription>
|
||||
{t('main.create-channel.guide.invalid-channel')}
|
||||
</DialogDescription>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<DialogClose>{t('v2.button.cancel')}</DialogClose>
|
||||
<Button onClick={() => jumpStepByKey('channel-info')}>
|
||||
{t('v2.button.confirm')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
));
|
||||
};
|
||||
|
||||
const { mutate } = useOAIMutation({
|
||||
method: 'post',
|
||||
path: '/api/admin/projects/{projectId}/channels',
|
||||
pathParams: { projectId },
|
||||
queryOptions: {
|
||||
async onSuccess(data) {
|
||||
await refetch();
|
||||
await router.replace({
|
||||
pathname: Path.CREATE_CHANNEL_COMPLETE,
|
||||
query: { projectId, channelId: data?.id },
|
||||
});
|
||||
reset();
|
||||
},
|
||||
onError(error) {
|
||||
if (error.code === ErrorCode.Channel.ChannelAlreadyExists) {
|
||||
openCreateChannelError();
|
||||
} else {
|
||||
toast.error(error.message);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const isDefaultInput = useMemo(
|
||||
() => isObjectEqual(CREATE_CHANNEL_DEFAULT_INPUT, input),
|
||||
[input],
|
||||
);
|
||||
|
||||
useWarnIfSavedChanges(
|
||||
!isDefaultInput,
|
||||
Path.CREATE_CHANNEL_COMPLETE.replace(
|
||||
'[projectId]',
|
||||
router.query.projectId as string,
|
||||
),
|
||||
);
|
||||
|
||||
const onComplete = () => {
|
||||
mutate({
|
||||
...input.channelInfo,
|
||||
fields: input.fields.filter((v) => !isDefaultField(v)),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<CreateInputTemplate
|
||||
currentStepIndex={currentStep.index}
|
||||
lastStep={CREATE_CHANNEL_MAIN_STEP_LIST.length - 1}
|
||||
onNext={nextStep}
|
||||
onPrev={prevStep}
|
||||
title={CREATE_CHANNEL_STEPPER_TEXT[currentStep.key]}
|
||||
actionButton={actionButton}
|
||||
onComplete={onComplete}
|
||||
validate={validate}
|
||||
disableNextBtn={disableNextBtn ?? isLoading}
|
||||
helpText={CREATE_CHANNEL_HELP_TEXT[currentStep.key]}
|
||||
onClickBack={onClickBack}
|
||||
isRequiredStep={CREATE_CHANNEL_REQUIRED_STEP[currentStep.key]}
|
||||
>
|
||||
{children}
|
||||
</CreateInputTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateChannelInputTemplate;
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import { CreationLayout, CreationStepper, Path } from '@/shared';
|
||||
|
||||
import { useCreateChannelStore } from '../create-channel-model';
|
||||
import { CREATE_CHANNEL_MAIN_STEP_LIST } from '../create-channel-type';
|
||||
import {
|
||||
CREATE_CHANNEL_COMPONENTS,
|
||||
CREATE_CHANNEL_STEPPER_TEXT,
|
||||
} from '../create-channel.constant';
|
||||
|
||||
interface IProps {}
|
||||
|
||||
const CreateChannel: React.FC<IProps> = () => {
|
||||
const { currentStep } = useCreateChannelStore();
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<CreationLayout
|
||||
title={t('main.create-channel.title')}
|
||||
leftPanel={
|
||||
<CreationStepper
|
||||
steps={CREATE_CHANNEL_MAIN_STEP_LIST}
|
||||
currentStepIndex={currentStep.index}
|
||||
stepTitle={CREATE_CHANNEL_STEPPER_TEXT}
|
||||
/>
|
||||
}
|
||||
leftBottom={
|
||||
<div className="flex justify-end">
|
||||
<Image
|
||||
src={`/assets/images/create-channel-${currentStep.key}.svg`}
|
||||
alt="Create Project"
|
||||
className="align"
|
||||
width={320}
|
||||
height={320}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
onClickGoBack={() =>
|
||||
router.push({
|
||||
pathname: Path.PROJECT_MAIN,
|
||||
query: { projectId: router.query.projectId },
|
||||
})
|
||||
}
|
||||
>
|
||||
{CREATE_CHANNEL_COMPONENTS[currentStep.key]}
|
||||
</CreationLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateChannel;
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export { default as CreateChannel } from './create-channel.ui';
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
|
||||
import { useAllChannels } from '@/shared';
|
||||
import { EMPTY_FUNCTION } from '@/shared/utils/empty-function';
|
||||
import type { ChannelInfo } from '@/entities/channel';
|
||||
import { ChannelInfoForm, channelInfoSchema } from '@/entities/channel';
|
||||
|
||||
import { useCreateChannelStore } from '../create-channel-model';
|
||||
import CreateChannelInputTemplate from './create-channel-input-template.ui';
|
||||
|
||||
interface IProps {}
|
||||
|
||||
const InputChannelInfoStep: React.FC<IProps> = () => {
|
||||
const { onChangeInput, input, setEditingStepIndex } = useCreateChannelStore();
|
||||
const router = useRouter();
|
||||
const projectId = Number(router.query.projectId);
|
||||
const { data } = useAllChannels(projectId);
|
||||
|
||||
const methods = useForm<ChannelInfo>({
|
||||
resolver: zodResolver(channelInfoSchema),
|
||||
defaultValues: input.channelInfo,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = methods.watch((values) => {
|
||||
const newValues = channelInfoSchema.safeParse(values);
|
||||
if (!newValues.data) return;
|
||||
onChangeInput('channelInfo', newValues.data);
|
||||
setEditingStepIndex(0);
|
||||
});
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<CreateChannelInputTemplate
|
||||
validate={async () => {
|
||||
const isValid = await methods.trigger();
|
||||
await methods.handleSubmit(EMPTY_FUNCTION)();
|
||||
const isDuplicated = data?.items.some(
|
||||
(v) => v.name === methods.getValues('name'),
|
||||
);
|
||||
if (isDuplicated) {
|
||||
methods.setError('name', { message: 'Duplicated name' });
|
||||
return false;
|
||||
}
|
||||
return isValid;
|
||||
}}
|
||||
>
|
||||
<FormProvider {...methods}>
|
||||
<ChannelInfoForm type="create" />
|
||||
</FormProvider>
|
||||
</CreateChannelInputTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputChannelInfoStep;
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { PreviewFieldTable } from '@/entities/field';
|
||||
|
||||
import { useCreateChannelStore } from '../create-channel-model';
|
||||
import CreateChannelInputTemplate from './create-channel-input-template.ui';
|
||||
|
||||
interface IProps {}
|
||||
|
||||
const InputFieldPreviewStep: React.FC<IProps> = () => {
|
||||
const { input, jumpStepByKey } = useCreateChannelStore();
|
||||
|
||||
return (
|
||||
<CreateChannelInputTemplate onClickBack={() => jumpStepByKey('field')}>
|
||||
<PreviewFieldTable
|
||||
fields={input.fields.filter((v) => v.status === 'ACTIVE')}
|
||||
/>
|
||||
</CreateChannelInputTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputFieldPreviewStep;
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { useOverlay } from '@toss/use-overlay';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import { Icon, ToggleGroup, ToggleGroupItem } from '@ufb/react';
|
||||
|
||||
import { FieldSettingSheet } from '@/entities/field';
|
||||
import type { FieldInfo } from '@/entities/field';
|
||||
import FieldTable from '@/entities/field/ui/field-table.ui';
|
||||
|
||||
import { useCreateChannelStore } from '../create-channel-model';
|
||||
import CreateChannelInputTemplate from './create-channel-input-template.ui';
|
||||
|
||||
interface IProps {}
|
||||
|
||||
const InputFieldStep: React.FC<IProps> = () => {
|
||||
const { input, onChangeInput, jumpStepByKey } = useCreateChannelStore();
|
||||
const fields = input.fields;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const overlay = useOverlay();
|
||||
|
||||
const createField = (field: FieldInfo) => {
|
||||
onChangeInput(
|
||||
'fields',
|
||||
input.fields.concat({ ...field, order: fields.length }),
|
||||
);
|
||||
};
|
||||
|
||||
const updateField = (input: { index: number; field: FieldInfo }) => {
|
||||
const { field, index } = input;
|
||||
onChangeInput(
|
||||
'fields',
|
||||
fields.map((v, i) => (i === index ? field : v)),
|
||||
);
|
||||
};
|
||||
|
||||
const deleteField = ({ index }: { index: number }) => {
|
||||
onChangeInput(
|
||||
'fields',
|
||||
input.fields.filter((_, i) => i !== index),
|
||||
);
|
||||
};
|
||||
|
||||
const openFieldFormSheet = (input?: { index: number; field: FieldInfo }) => {
|
||||
overlay.open(({ close, isOpen }) => (
|
||||
<FieldSettingSheet
|
||||
isOpen={isOpen}
|
||||
close={close}
|
||||
onSubmit={(newField) => {
|
||||
if (input) {
|
||||
updateField({ index: input.index, field: newField });
|
||||
return;
|
||||
}
|
||||
createField(newField);
|
||||
}}
|
||||
fieldRows={fields}
|
||||
data={input?.field}
|
||||
onClickDelete={
|
||||
input ?
|
||||
() => {
|
||||
deleteField({ index: input.index });
|
||||
close();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<CreateChannelInputTemplate
|
||||
actionButton={
|
||||
<ToggleGroup type="single" value="">
|
||||
<ToggleGroupItem value="item-1" onClick={() => openFieldFormSheet()}>
|
||||
<Icon name="RiAddLine" />
|
||||
{t('v2.button.name.add', { name: 'Field' })}
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="item-2"
|
||||
onClick={() => jumpStepByKey('field-preview')}
|
||||
>
|
||||
<Icon name="RiEyeLine" />
|
||||
{t('v2.text.preview')}
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
}
|
||||
>
|
||||
<FieldTable
|
||||
fields={input.fields}
|
||||
onClickRow={(index, field) => openFieldFormSheet({ index, field })}
|
||||
disableFilter
|
||||
/>
|
||||
</CreateChannelInputTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputFieldStep;
|
||||
Reference in new issue
Block a user