Skip to content

Commit 57efdec

Browse files
committed
feat: general setting
1 parent b12414d commit 57efdec

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2345
-652
lines changed

client/src/apis/channel.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
TChannelType,
77
TChannelWithChannelType,
88
} from '@/types/channel'
9-
import { TBaseResponse, TResPagination } from '@/types/share'
9+
import { TBaseResponse, TResPagination, TSelectResponse } from '@/types/share'
1010

1111
class ChannelApi {
1212
getChannelType(id: string): Promise<TBaseResponse<TChannelType>> {
@@ -45,6 +45,14 @@ class ChannelApi {
4545
data: { ids },
4646
})
4747
}
48+
49+
getChannelsForSelect(
50+
flowId: string,
51+
): Promise<TBaseResponse<TSelectResponse<string, string>[]>> {
52+
return http_client.get(`${ENDPOINTS.CHANNEL.INDEX}/for-select`, {
53+
params: { flowId },
54+
})
55+
}
4856
}
4957

5058
export const channelApi = new ChannelApi()

client/src/apis/flow.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import { ENDPOINTS } from '@/constants'
22
import http_client from '@/lib/http-client'
33
import { TFlowInput } from '@/lib/schema/flow-input'
44
import { TFLow } from '@/types/flow'
5-
import { TBaseQuery, TBaseResponse, TResPagination } from '@/types/share'
5+
import {
6+
TBaseQuery,
7+
TBaseResponse,
8+
TResPagination,
9+
TSelectResponse,
10+
} from '@/types/share'
611

712
export class FlowApi {
813
create(data: TFlowInput): Promise<TBaseResponse<TFLow>> {
@@ -21,7 +26,7 @@ export class FlowApi {
2126
return http_client.post(`${ENDPOINTS.FLOW.PUBLISH}/${id}`)
2227
}
2328

24-
get(id: string) {
29+
get(id: string): Promise<TBaseResponse<TFLow>> {
2530
return http_client.get(`${ENDPOINTS.FLOW.GET_ONE}/${id}`)
2631
}
2732

@@ -30,6 +35,14 @@ export class FlowApi {
3035
): Promise<TResPagination<Pick<TFLow, 'id' | 'name' | 'publishAt'>>> {
3136
return http_client.get(ENDPOINTS.FLOW.INDEX, { params: q })
3237
}
38+
39+
getFlowsForSelect(
40+
channelId: string,
41+
): Promise<TBaseResponse<TSelectResponse<string, string>[]>> {
42+
return http_client.get(`${ENDPOINTS.FLOW.INDEX}/for-select`, {
43+
params: { channelId },
44+
})
45+
}
3346
}
3447

3548
export const flowApi = new FlowApi()

client/src/app.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createBrowserRouter, redirect } from 'react-router-dom'
1+
import { Outlet, createBrowserRouter, redirect } from 'react-router-dom'
22
import i18n from './i18n'
33
import {
44
Channels,
@@ -11,7 +11,6 @@ import {
1111
Register,
1212
SetPassword,
1313
} from '@/pages'
14-
import { useAppLayoutStore } from '@/store'
1514

1615
import {
1716
AppLayout,
@@ -29,13 +28,25 @@ import {
2928
articlesLoader,
3029
authLoader,
3130
channelsLoader,
31+
flowDetailLoader,
3232
flowsLoader,
3333
settingLoader,
3434
} from './lib/loader'
3535
import HelpDetail from './pages/help-detail'
36+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
37+
38+
const App = () => {
39+
return (
40+
<div>
41+
<ReactQueryDevtools />
42+
<Outlet />
43+
</div>
44+
)
45+
}
3646

3747
export const router = createBrowserRouter([
3848
{
49+
Component: App,
3950
children: [
4051
{
4152
loader: authLoader,
@@ -143,6 +154,7 @@ export const router = createBrowserRouter([
143154
{
144155
path: `${ROUTES.PRIVATE.CHAT_BOT.INDEX}/:id`,
145156
Component: ChatBotDetail,
157+
loader: flowDetailLoader,
146158
},
147159
],
148160
},

client/src/components/forms/channel.tsx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,25 @@ import { zodResolver } from '@hookform/resolvers/zod'
2424
import { ChannelType } from '@/types/channel'
2525
import { useMemo } from 'react'
2626
import { useDidUpdate } from '@/hooks/use-did-update'
27+
import { queryFlowsForSelectOption } from '@/lib/query-options/flow'
2728

2829
type Props = {
2930
id?: string
3031
onSubmit?: (data: TChannelInput) => void
3132
defaultValues?: TChannelInput
33+
channelId?: string
3234
}
3335

3436
const ChannelForm = ({
3537
id = 'channel-form',
3638
onSubmit,
3739
defaultValues,
40+
channelId,
3841
}: Props) => {
3942
const { t } = useTranslation('forms')
43+
const { data: flows } = useQuery(queryFlowsForSelectOption(channelId || ''))
44+
const { data: types } = useQuery(queryChannelTypesOption)
45+
4046
const schema = useChannelSchema()
4147
const form = useForm<TChannelInput>({
4248
resolver: zodResolver(schema),
@@ -46,7 +52,6 @@ const ChannelForm = ({
4652
...defaultValues,
4753
},
4854
})
49-
const { data: types } = useQuery(queryChannelTypesOption)
5055

5156
const channelTypeId = form.watch('channelTypeId')
5257

@@ -127,6 +132,34 @@ const ChannelForm = ({
127132
</FormItem>
128133
)}
129134
/>
135+
<FormField
136+
control={form.control}
137+
name='flowId'
138+
render={({ field }) => (
139+
<FormItem>
140+
<FormLabel>
141+
<Label>{t('active.label')}</Label>
142+
</FormLabel>
143+
144+
<Select onValueChange={field.onChange} defaultValue={field.value}>
145+
<FormControl>
146+
<SelectTrigger>
147+
<SelectValue placeholder='Select a verified email to display' />
148+
</SelectTrigger>
149+
</FormControl>
150+
<SelectContent>
151+
{flows?.map((flow) => {
152+
return (
153+
<SelectItem key={flow.value} value={flow.value}>
154+
{flow.label}
155+
</SelectItem>
156+
)
157+
})}
158+
</SelectContent>
159+
</Select>
160+
</FormItem>
161+
)}
162+
/>
130163
<FormField
131164
name='channelTypeId'
132165
control={form.control}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { useForm } from 'react-hook-form'
2+
import {
3+
Form,
4+
FormControl,
5+
FormDescription,
6+
FormField,
7+
FormItem,
8+
FormLabel,
9+
FormMessage,
10+
Input,
11+
Label,
12+
MultipleSelect,
13+
Select,
14+
SelectContent,
15+
SelectItem,
16+
SelectTrigger,
17+
SelectValue,
18+
} from '../ui'
19+
import { useErrorsLngChange } from '@/hooks/use-errors-lng-change'
20+
import { useTranslation } from 'react-i18next'
21+
import { zodResolver } from '@hookform/resolvers/zod'
22+
import { TFlowInput, useFlowInputSchema } from '@/lib/schema/flow-input'
23+
import { queryChannelsForSelectOption } from '@/lib/query-options/channel'
24+
import { useParams } from 'react-router-dom'
25+
import { useSuspenseQuery } from '@tanstack/react-query'
26+
import i18n from '@/i18n'
27+
28+
const LANGS: Record<string, string> = {
29+
vi: i18n.t('common:langs.vi'),
30+
en: i18n.t('common:langs.en'),
31+
}
32+
33+
type Props = {
34+
id?: string
35+
onSubmit?: (data: TFlowInput) => void
36+
defaultValues?: TFlowInput
37+
}
38+
39+
export const GeneralSettingForm = ({
40+
id = 'general-setting-form',
41+
onSubmit,
42+
defaultValues,
43+
}: Props) => {
44+
const { id: flowId } = useParams()
45+
const { data } = useSuspenseQuery(
46+
queryChannelsForSelectOption(flowId as string),
47+
)
48+
const { t } = useTranslation('forms')
49+
const schema = useFlowInputSchema()
50+
const form = useForm<TFlowInput>({
51+
resolver: zodResolver(schema),
52+
mode: 'onChange',
53+
defaultValues: {
54+
...defaultValues,
55+
channelIds: data
56+
.filter((item) => item.isSelected)
57+
.map((item) => item.value),
58+
},
59+
})
60+
61+
useErrorsLngChange(form)
62+
63+
const handleSubmit = (data: TFlowInput) => {
64+
if (onSubmit) {
65+
onSubmit(data)
66+
}
67+
}
68+
69+
return (
70+
<Form {...form}>
71+
<form
72+
className='space-y-3'
73+
id={id}
74+
onSubmit={form.handleSubmit(handleSubmit)}
75+
>
76+
<FormField
77+
name='channelIds'
78+
control={form.control}
79+
render={({ field }) => (
80+
<FormItem>
81+
<FormLabel>{t('channels.label')}</FormLabel>
82+
<FormControl>
83+
<MultipleSelect
84+
options={
85+
data?.map((item) => ({
86+
label: item.label,
87+
value: item.value,
88+
})) || []
89+
}
90+
onChange={field.onChange}
91+
values={field.value}
92+
placeholder={t('channels.placeholder')}
93+
/>
94+
</FormControl>
95+
<FormMessage />
96+
</FormItem>
97+
)}
98+
/>
99+
<FormField
100+
control={form.control}
101+
name='settings'
102+
render={({ field }) => (
103+
<FormItem>
104+
<FormLabel>
105+
<Label>{t('bot_lang.label')}</Label>
106+
</FormLabel>
107+
108+
<Select
109+
onValueChange={(value) => {
110+
field.onChange([
111+
{
112+
type: 'language',
113+
value,
114+
default: true,
115+
label: 'Language',
116+
},
117+
])
118+
}}
119+
defaultValue={
120+
field.value?.find((setting) => setting.type === 'language')
121+
?.value
122+
}
123+
>
124+
<FormControl>
125+
<SelectTrigger>
126+
<SelectValue placeholder={t('bot_lang.placeholder')} />
127+
</SelectTrigger>
128+
</FormControl>
129+
<SelectContent>
130+
{Object.keys(LANGS).map((lang) => {
131+
return (
132+
<SelectItem key={lang} value={lang}>
133+
{LANGS[lang]}
134+
</SelectItem>
135+
)
136+
})}
137+
</SelectContent>
138+
</Select>
139+
<FormDescription>{t('bot_lang.description')}</FormDescription>
140+
</FormItem>
141+
)}
142+
/>
143+
</form>
144+
</Form>
145+
)
146+
}
147+
148+
export default GeneralSettingForm

client/src/components/pages/channels/row-action.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ const RowActions = ({ row }: Props) => {
8484
: undefined,
8585
active: row.original.active,
8686
contactName: row.original.contactName,
87+
flowId: row.original.flowId,
8788
}}
8889
onSubmit={async (data) => {
8990
await updateChannelMutation.mutateAsync({
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { TFlowInput } from '@/lib/schema/flow-input'
2+
import { createContext, useContext } from 'react'
3+
4+
type FlowCtx = {
5+
flow: TFlowInput
6+
}
7+
8+
const FlowContext = createContext<FlowCtx | undefined>(undefined)
9+
10+
type Props = {
11+
flow: TFlowInput
12+
children: React.ReactNode
13+
}
14+
15+
export const FlowProvider = ({ children, flow }: Props) => {
16+
return (
17+
<FlowContext.Provider value={{ flow }}>{children}</FlowContext.Provider>
18+
)
19+
}
20+
21+
export const useFlowCtx = () => {
22+
const ctx = useContext(FlowContext)
23+
if (!ctx) {
24+
throw new Error('useFlowCtx must be used within a FlowProvider')
25+
}
26+
return ctx
27+
}

0 commit comments

Comments
 (0)