feat: sets selection modal
This commit is contained in:
parent
35625b281b
commit
ae1ab39ed4
|
|
@ -61,17 +61,21 @@ import { TagsComponent } from './tags.component';
|
|||
import { RepeatComponent } from '@gitroom/frontend/components/launches/repeat.component';
|
||||
import { MergePost } from '@gitroom/frontend/components/launches/merge.post';
|
||||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
import { CreatePostDto } from '@gitroom/nestjs-libraries/dtos/posts/create.post.dto';
|
||||
import { uniq } from 'lodash';
|
||||
import { SetContext } from '@gitroom/frontend/components/launches/set.context';
|
||||
function countCharacters(text: string, type: string): number {
|
||||
if (type !== 'x') {
|
||||
return text.length;
|
||||
}
|
||||
return weightedLength(text);
|
||||
}
|
||||
|
||||
export const AddEditModal: FC<{
|
||||
date: dayjs.Dayjs;
|
||||
integrations: Integrations[];
|
||||
allIntegrations?: Integrations[];
|
||||
setId?: string;
|
||||
set?: CreatePostDto;
|
||||
addEditSets?: (data: any) => void;
|
||||
reopenModal: () => void;
|
||||
mutate: () => void;
|
||||
|
|
@ -95,7 +99,9 @@ export const AddEditModal: FC<{
|
|||
padding,
|
||||
customClose,
|
||||
addEditSets,
|
||||
set,
|
||||
} = props;
|
||||
|
||||
const [customer, setCustomer] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
|
@ -105,17 +111,24 @@ export const AddEditModal: FC<{
|
|||
// selected integrations to allow edit
|
||||
const [selectedIntegrations, setSelectedIntegrations] = useStateCallback<
|
||||
Integrations[]
|
||||
>([]);
|
||||
>(
|
||||
set
|
||||
? ints.filter(
|
||||
(f) =>
|
||||
uniq(set.posts.flatMap((p) => p.integration.id)).indexOf(f.id) > -1
|
||||
)
|
||||
: []
|
||||
);
|
||||
const integrations = useMemo(() => {
|
||||
if (!customer) {
|
||||
return ints;
|
||||
}
|
||||
const list = ints.filter((f) => f?.customer?.id === customer);
|
||||
if (list.length === 1) {
|
||||
if (list.length === 1 && !set) {
|
||||
setSelectedIntegrations([list[0]]);
|
||||
}
|
||||
return list;
|
||||
}, [customer, ints]);
|
||||
}, [customer, ints, set]);
|
||||
const [dateState, setDateState] = useState(date);
|
||||
|
||||
// hook to open a new modal
|
||||
|
|
@ -134,6 +147,12 @@ export const AddEditModal: FC<{
|
|||
>(
|
||||
onlyValues
|
||||
? onlyValues
|
||||
: set
|
||||
? set?.posts?.[0].value || [
|
||||
{
|
||||
content: '',
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
content: '',
|
||||
|
|
@ -467,18 +486,24 @@ export const AddEditModal: FC<{
|
|||
body: JSON.stringify(data),
|
||||
});
|
||||
existingData.group = makeId(10);
|
||||
mutate();
|
||||
toaster.show(
|
||||
!existingData.integration
|
||||
? 'Added successfully'
|
||||
: 'Updated successfully'
|
||||
);
|
||||
|
||||
if (!addEditSets) {
|
||||
mutate();
|
||||
toaster.show(
|
||||
!existingData.integration
|
||||
? 'Added successfully'
|
||||
: 'Updated successfully'
|
||||
);
|
||||
}
|
||||
if (customClose) {
|
||||
setTimeout(() => {
|
||||
customClose();
|
||||
}, 2000);
|
||||
}
|
||||
modal.closeAll();
|
||||
|
||||
if (!addEditSets) {
|
||||
modal.closeAll();
|
||||
}
|
||||
},
|
||||
[
|
||||
inter,
|
||||
|
|
@ -578,7 +603,7 @@ export const AddEditModal: FC<{
|
|||
}, [data, postFor, selectedIntegrations]);
|
||||
useClickOutside(askClose);
|
||||
return (
|
||||
<>
|
||||
<SetContext.Provider value={{ set }}>
|
||||
{user?.tier?.ai && (
|
||||
<CopilotPopup
|
||||
hitEscapeToClose={false}
|
||||
|
|
@ -658,7 +683,7 @@ Here are the things you can do:
|
|||
<PickPlatforms
|
||||
toolTip={true}
|
||||
integrations={integrations.filter((f) => !f.disabled)}
|
||||
selectedIntegrations={[]}
|
||||
selectedIntegrations={set ? selectedIntegrations : []}
|
||||
singleSelect={false}
|
||||
onChange={setSelectedIntegrations}
|
||||
isMain={true}
|
||||
|
|
@ -938,6 +963,7 @@ Here are the things you can do:
|
|||
{!!selectedIntegrations.length && (
|
||||
<div className="flex-1 flex flex-col p-[16px] pt-0">
|
||||
<ProvidersOptions
|
||||
hideEditOnlyThis={!!set}
|
||||
allIntegrations={props.allIntegrations || []}
|
||||
integrations={selectedIntegrations}
|
||||
editorValue={value}
|
||||
|
|
@ -947,6 +973,6 @@ Here are the things you can do:
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</SetContext.Provider>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ export const CalendarContext = createContext({
|
|||
currentYear: dayjs().year(),
|
||||
currentMonth: dayjs().month(),
|
||||
customer: null as string | null,
|
||||
sets: [] as { name: string; id: string; content: string[] }[],
|
||||
comments: [] as Array<{
|
||||
date: string;
|
||||
total: number;
|
||||
|
|
@ -142,6 +143,13 @@ export const CalendarWeekProvider: FC<{
|
|||
refreshWhenHidden: false,
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
|
||||
const setList = useCallback(async () => {
|
||||
return (await fetch('/sets')).json();
|
||||
}, []);
|
||||
|
||||
const { data: sets, mutate } = useSWR('sets', setList);
|
||||
|
||||
const setFiltersWrapper = useCallback(
|
||||
(filters: {
|
||||
currentDay: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
||||
|
|
@ -202,6 +210,7 @@ export const CalendarWeekProvider: FC<{
|
|||
setFilters: setFiltersWrapper,
|
||||
changeDate,
|
||||
comments,
|
||||
sets: sets || [],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -336,6 +336,7 @@ export const CalendarColumn: FC<{
|
|||
changeDate,
|
||||
display,
|
||||
reloadCalendarView,
|
||||
sets,
|
||||
} = useCalendar();
|
||||
const toaster = useToaster();
|
||||
const modal = useModals();
|
||||
|
|
@ -547,8 +548,13 @@ export const CalendarColumn: FC<{
|
|||
},
|
||||
[integrations]
|
||||
);
|
||||
|
||||
const addModal = useCallback(async () => {
|
||||
const signature = await (await fetch('/signatures/default')).json();
|
||||
const set = !sets.length ? undefined : await new Promise(() => {
|
||||
|
||||
});
|
||||
|
||||
modal.openModal({
|
||||
closeOnClickOutside: false,
|
||||
closeOnEscape: false,
|
||||
|
|
@ -583,7 +589,7 @@ export const CalendarColumn: FC<{
|
|||
size: '80%',
|
||||
// title: `Adding posts for ${getDate.format('DD/MM/YYYY HH:mm')}`,
|
||||
});
|
||||
}, [integrations, getDate]);
|
||||
}, [integrations, getDate, sets]);
|
||||
const openStatistics = useCallback(
|
||||
(id: string) => () => {
|
||||
modal.openModal({
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ export const useValues = (
|
|||
criteriaMode: 'all',
|
||||
});
|
||||
|
||||
console.log(form.formState.errors);
|
||||
const getValues = useMemo(() => {
|
||||
return () => ({
|
||||
...form.getValues(),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { ShowAllProviders } from '@gitroom/frontend/components/launches/provider
|
|||
import dayjs from 'dayjs';
|
||||
import { useStateCallback } from '@gitroom/react/helpers/use.state.callback';
|
||||
export const ProvidersOptions: FC<{
|
||||
hideEditOnlyThis: boolean;
|
||||
integrations: Integrations[];
|
||||
allIntegrations: Integrations[];
|
||||
editorValue: Array<{
|
||||
|
|
@ -14,7 +15,7 @@ export const ProvidersOptions: FC<{
|
|||
}>;
|
||||
date: dayjs.Dayjs;
|
||||
}> = (props) => {
|
||||
const { integrations, editorValue, date } = props;
|
||||
const { integrations, editorValue, date, hideEditOnlyThis } = props;
|
||||
const [selectedIntegrations, setSelectedIntegrations] = useStateCallback([
|
||||
integrations[0],
|
||||
]);
|
||||
|
|
@ -42,6 +43,7 @@ export const ProvidersOptions: FC<{
|
|||
}}
|
||||
>
|
||||
<ShowAllProviders
|
||||
hideEditOnlyThis={hideEditOnlyThis}
|
||||
value={editorValue}
|
||||
integrations={integrations}
|
||||
selectedProvider={selectedIntegrations?.[0]}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import useSWR from 'swr';
|
|||
import { InternalChannels } from '@gitroom/frontend/components/launches/internal.channels';
|
||||
import { MergePost } from '@gitroom/frontend/components/launches/merge.post';
|
||||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
import { useSet } from '@gitroom/frontend/components/launches/set.context';
|
||||
|
||||
// Simple component to change back to settings on after changing tab
|
||||
export const SetTab: FC<{
|
||||
|
|
@ -110,6 +111,7 @@ export const withProvider = function <T extends object>(
|
|||
}>;
|
||||
hideMenu?: boolean;
|
||||
show: boolean;
|
||||
hideEditOnlyThis?: boolean;
|
||||
}) => {
|
||||
const existingData = useExistingData();
|
||||
const t = useT();
|
||||
|
|
@ -170,9 +172,13 @@ export const withProvider = function <T extends object>(
|
|||
}
|
||||
);
|
||||
|
||||
const set = useSet();
|
||||
|
||||
// this is a smart function, it updates the global value without updating the states (too heavy) and set the settings validation
|
||||
const form = useValues(
|
||||
existingData.settings,
|
||||
set?.set
|
||||
? set.set.posts.find((p) => p.integration.id === props.id).settings
|
||||
: existingData.settings,
|
||||
props.id,
|
||||
props.identifier,
|
||||
editInPlace ? InPlaceValue : props.value,
|
||||
|
|
@ -451,7 +457,7 @@ export const withProvider = function <T extends object>(
|
|||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{!existingData.integration && (
|
||||
{!existingData.integration && !props.hideEditOnlyThis && (
|
||||
<div className="flex-1 flex">
|
||||
<Button
|
||||
className="text-white rounded-[4px] flex-1 !bg-red-700 overflow-hidden whitespace-nowrap"
|
||||
|
|
|
|||
|
|
@ -122,13 +122,14 @@ export const Providers = [
|
|||
];
|
||||
export const ShowAllProviders: FC<{
|
||||
integrations: Integrations[];
|
||||
hideEditOnlyThis: boolean;
|
||||
value: Array<{
|
||||
content: string;
|
||||
id?: string;
|
||||
}>;
|
||||
selectedProvider?: Integrations;
|
||||
}> = (props) => {
|
||||
const { integrations, value, selectedProvider } = props;
|
||||
const { integrations, value, selectedProvider, hideEditOnlyThis } = props;
|
||||
return (
|
||||
<>
|
||||
{integrations.map((integration) => {
|
||||
|
|
@ -145,6 +146,7 @@ export const ShowAllProviders: FC<{
|
|||
}
|
||||
return (
|
||||
<ProviderComponent
|
||||
hideEditOnlyThis={hideEditOnlyThis}
|
||||
key={integration.id}
|
||||
{...integration}
|
||||
value={value}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
import { createContext, useContext } from 'react';
|
||||
import { CreatePostDto } from '@gitroom/nestjs-libraries/dtos/posts/create.post.dto';
|
||||
|
||||
export const SetContext = createContext<{set?: CreatePostDto}>({});
|
||||
export const useSet = () => useContext(SetContext);
|
||||
|
|
@ -7,12 +7,7 @@ import useSWR from 'swr';
|
|||
import { useUser } from '@gitroom/frontend/components/layout/user.context';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { Input } from '@gitroom/react/form/input';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { object, string } from 'yup';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Textarea } from '@gitroom/react/form/textarea';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
import clsx from 'clsx';
|
||||
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
|
||||
|
|
@ -20,6 +15,48 @@ import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
|||
import { AddEditModal } from '@gitroom/frontend/components/launches/add.edit.model';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const SaveSetModal: FC<{
|
||||
postData: any;
|
||||
initialValue?: string;
|
||||
onSave: (name: string) => void;
|
||||
onCancel: () => void;
|
||||
}> = ({ postData, onSave, onCancel, initialValue }) => {
|
||||
const [name, setName] = useState(initialValue);
|
||||
const t = useT();
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (name.trim()) {
|
||||
onSave(name.trim());
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
||||
<div>
|
||||
<Input
|
||||
label="Set Name"
|
||||
translationKey="label_set_name"
|
||||
name="setName"
|
||||
value={name}
|
||||
disableForm={true}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Enter a name for this set"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button type="button" secondary onClick={onCancel}>
|
||||
{t('cancel', 'Cancel')}
|
||||
</Button>
|
||||
<Button type="submit" disabled={!name.trim()}>
|
||||
{t('save', 'Save')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export const Sets: FC = () => {
|
||||
const fetch = useFetch();
|
||||
const user = useUser();
|
||||
|
|
@ -41,7 +78,7 @@ export const Sets: FC = () => {
|
|||
const { data, mutate } = useSWR('sets', list);
|
||||
|
||||
const addSet = useCallback(
|
||||
(data?: any) => () => {
|
||||
(params?: { id?: string; name?: string; content?: string }) => () => {
|
||||
modal.openModal({
|
||||
closeOnClickOutside: false,
|
||||
closeOnEscape: false,
|
||||
|
|
@ -54,8 +91,40 @@ export const Sets: FC = () => {
|
|||
allIntegrations={integrations.map((p: any) => ({
|
||||
...p,
|
||||
}))}
|
||||
{...(params?.id ? { set: JSON.parse(params.content) } : {})}
|
||||
addEditSets={(data) => {
|
||||
console.log('save', data);
|
||||
modal.openModal({
|
||||
title: 'Save as Set',
|
||||
classNames: {
|
||||
modal: 'bg-sixth text-textColor',
|
||||
title: 'text-textColor',
|
||||
},
|
||||
children: (
|
||||
<SaveSetModal
|
||||
initialValue={params?.name || ''}
|
||||
postData={data}
|
||||
onSave={async (name: string) => {
|
||||
try {
|
||||
await fetch('/sets', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
...(params?.id ? { id: params.id } : {}),
|
||||
name,
|
||||
content: JSON.stringify(data),
|
||||
}),
|
||||
});
|
||||
modal.closeAll();
|
||||
mutate();
|
||||
toaster.show('Set saved successfully', 'success');
|
||||
} catch (error) {
|
||||
toaster.show('Failed to save set', 'warning');
|
||||
}
|
||||
}}
|
||||
onCancel={() => modal.closeAll()}
|
||||
/>
|
||||
),
|
||||
size: 'md',
|
||||
});
|
||||
}}
|
||||
reopenModal={() => {}}
|
||||
mutate={() => {}}
|
||||
|
|
@ -130,90 +199,3 @@ export const Sets: FC = () => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const details = object().shape({
|
||||
name: string().required(),
|
||||
content: string().required(),
|
||||
});
|
||||
|
||||
export const AddOrEditSet: FC<{
|
||||
data?: any;
|
||||
reload: () => void;
|
||||
}> = (props) => {
|
||||
const { data, reload } = props;
|
||||
const fetch = useFetch();
|
||||
const modal = useModals();
|
||||
const toast = useToaster();
|
||||
|
||||
const form = useForm({
|
||||
resolver: yupResolver(details),
|
||||
values: {
|
||||
name: data?.name || '',
|
||||
content: data?.content || '',
|
||||
},
|
||||
});
|
||||
|
||||
const callBack = useCallback(
|
||||
async (values: any) => {
|
||||
// TODO: Implement save functionality
|
||||
console.log('Save set functionality to be implemented', values);
|
||||
modal.closeAll();
|
||||
reload();
|
||||
},
|
||||
[data]
|
||||
);
|
||||
|
||||
const t = useT();
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(callBack)}>
|
||||
<div className="relative flex gap-[20px] flex-col flex-1 rounded-[4px] border border-customColor6 bg-sixth p-[16px] pt-0 w-[500px]">
|
||||
<TopTitle title={data ? 'Edit set' : 'Add set'} />
|
||||
<button
|
||||
className="outline-none absolute end-[20px] top-[15px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
|
||||
type="button"
|
||||
onClick={modal.closeAll}
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div>
|
||||
<Input
|
||||
label="Name"
|
||||
translationKey="label_name"
|
||||
{...form.register('name')}
|
||||
/>
|
||||
<Textarea
|
||||
label="Content"
|
||||
translationKey="label_content"
|
||||
{...form.register('content')}
|
||||
/>
|
||||
<div className="flex gap-[10px]">
|
||||
<Button
|
||||
type="submit"
|
||||
className="mt-[24px]"
|
||||
disabled={!form.formState.isValid}
|
||||
>
|
||||
{t('save', 'Save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue