openreplay/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx
2022-12-30 17:59:56 +01:00

305 lines
8.8 KiB
TypeScript

import React from 'react';
import { Icon, Button, Checkbox } from 'UI';
import { Duration } from 'luxon';
import { connect } from 'react-redux';
import { WriteNote, tagProps, TAGS, iTag, Note } from 'App/services/NotesService';
import { setCreateNoteTooltip, addNote, updateNote } from 'Duck/sessions';
import stl from './styles.module.css';
import { useStore } from 'App/mstore';
import { toast } from 'react-toastify';
import { fetchList as fetchSlack } from 'Duck/integrations/slack';
import { fetchList as fetchTeams } from 'Duck/integrations/teams';
import Select from 'Shared/Select';
import { TeamBadge } from 'Shared/SessionListContainer/components/Notes';
import { List } from 'immutable';
interface Props {
isVisible: boolean;
time: number;
setCreateNoteTooltip: (state: any) => void;
addNote: (note: Note) => void;
updateNote: (note: Note) => void;
sessionId: string;
isEdit: string;
editNote: WriteNote;
slackChannels: List<Record<string, any>>;
teamsChannels: List<Record<string, any>>;
fetchSlack: () => void;
fetchTeams: () => void;
}
function CreateNote({
isVisible,
time,
setCreateNoteTooltip,
sessionId,
addNote,
isEdit,
editNote,
updateNote,
slackChannels,
fetchSlack,
teamsChannels,
fetchTeams,
}: Props) {
const [text, setText] = React.useState('');
const [slackChannel, setSlackChannel] = React.useState('');
const [teamsChannel, setTeamsChannel] = React.useState('');
const [isPublic, setPublic] = React.useState(false);
const [tag, setTag] = React.useState<iTag>(TAGS[0]);
const [useTimestamp, setUseTs] = React.useState(true);
const [useSlack, setSlack] = React.useState(false);
const [useTeams, setTeams] = React.useState(false);
const inputRef = React.createRef<HTMLTextAreaElement>();
const { notesStore } = useStore();
React.useEffect(() => {
if (isEdit) {
setTag(editNote.tag);
setText(editNote.message);
setPublic(editNote.isPublic);
if (editNote.timestamp > 0) {
setUseTs(true);
}
}
}, [isEdit]);
React.useEffect(() => {
if (inputRef.current && isVisible) {
fetchSlack();
fetchTeams();
inputRef.current.focus();
}
}, [isVisible]);
const duration = Duration.fromMillis(time).toFormat('mm:ss');
const onSubmit = () => {
if (text === '') return;
const note: WriteNote = {
message: text,
tag,
timestamp: useTimestamp ? (isEdit ? editNote.timestamp : time) : -1,
isPublic,
};
const onSuccess = (noteId: string) => {
if (slackChannel) {
notesStore.sendSlackNotification(noteId, slackChannel);
}
if (teamsChannel) {
notesStore.sendMsTeamsNotification(noteId, teamsChannel);
}
};
if (isEdit) {
return notesStore
.updateNote(editNote.noteId, note)
.then((r) => {
toast.success('Note updated');
notesStore.fetchSessionNotes(sessionId).then((notes) => {
onSuccess(editNote.noteId);
updateNote(r);
});
})
.catch((e) => {
toast.error('Error updating note');
console.error(e);
})
.finally(() => {
setCreateNoteTooltip({ isVisible: false, time: 0 });
setText('');
setTag(undefined);
});
}
return notesStore
.addNote(sessionId, note)
.then((r) => {
onSuccess(r.noteId as unknown as string);
toast.success('Note added');
notesStore.fetchSessionNotes(sessionId).then((notes) => {
addNote(r);
});
})
.catch((e) => {
toast.error('Error adding note');
console.error(e);
})
.finally(() => {
setCreateNoteTooltip({ isVisible: false, time: 0 });
setText('');
setTag(undefined);
});
};
const closeTooltip = () => {
setCreateNoteTooltip({ isVisible: false, time: 100 });
};
const tagActive = (noteTag: iTag) => tag === noteTag;
const addTag = (tag: iTag) => {
setTag(tag);
};
const slackChannelsOptions = slackChannels
.map(({ webhookId, name }) => ({
value: webhookId,
label: name,
}))
.toJS() as unknown as { value: string; label: string }[];
const teamsChannelsOptions = teamsChannels
.map(({ webhookId, name }) => ({
value: webhookId,
label: name,
}))
.toJS() as unknown as { value: string; label: string }[];
slackChannelsOptions.unshift({ value: null, label: 'Pick a channel' });
teamsChannelsOptions.unshift({ value: null, label: 'Pick a channel' });
const changeSlackChannel = ({ value, name }: { value: Record<string, string>; name: string }) => {
setSlackChannel(value.value);
};
const changeTeamsChannel = ({ value, name }: { value: Record<string, string>; name: string }) => {
setTeamsChannel(value.value);
};
return (
<div
className={stl.noteTooltip}
style={{
width: 350,
left: 'calc(50% - 175px)',
display: isVisible ? 'flex' : 'none',
flexDirection: 'column',
gap: '1rem',
bottom: '15vh',
zIndex: 110,
}}
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center bg-gray-lightest">
<Icon name="quotes" size={20} />
<h3 className="text-xl ml-2 mr-4 font-semibold">Add Note</h3>
<div className="flex items-center cursor-pointer" onClick={() => setUseTs(!useTimestamp)}>
<Checkbox checked={useTimestamp} />
<span className="ml-1"> {`at ${duration}`} </span>
</div>
<div className="ml-auto cursor-pointer" onClick={closeTooltip}>
<Icon name="close" size={20} />
</div>
</div>
<div className="">
<textarea
ref={inputRef}
name="message"
id="message"
placeholder="Note..."
rows={3}
value={text}
autoFocus
onChange={(e) => setText(e.target.value)}
style={{
border: 'solid thin #ddd',
borderRadius: 3,
resize: 'none',
background: '#ffff',
}}
/>
</div>
<div className="flex items-center gap-2" style={{ lineHeight: '15px' }}>
{TAGS.map((tag) => (
<div
key={tag}
style={{
background: tagActive(tag) ? tagProps[tag] : 'rgba(0,0,0, 0.38)',
userSelect: 'none',
minWidth: 50,
fontSize: 11,
}}
className="cursor-pointer rounded-full justify-center px-2 py-1 text-white flex items-center gap-2"
onClick={() => addTag(tag)}
>
{tagActive(tag) ? <Icon name="check-circle-fill" color="white" size={13} /> : null}
<div>{tag}</div>
</div>
))}
</div>
{slackChannelsOptions.length > 0 ? (
<div className="flex flex-col">
<div className="flex items-center cursor-pointer" onClick={() => setSlack(!useSlack)}>
<Checkbox checked={useSlack} />
<span className="ml-1 mr-3"> Send to slack? </span>
</div>
{useSlack && (
<div>
<Select
options={slackChannelsOptions}
// @ts-ignore
defaultValue
// @ts-ignore
onChange={changeSlackChannel}
/>
</div>
)}
</div>
) : null}
{teamsChannelsOptions.length > 0 ? (
<div className="flex flex-col">
<div className="flex items-center cursor-pointer" onClick={() => setTeams(!useTeams)}>
<Checkbox checked={useTeams} />
<span className="ml-1 mr-3"> Send to teams? </span>
</div>
{useTeams && (
<div>
<Select
options={teamsChannelsOptions}
// @ts-ignore
defaultValue
// @ts-ignore
onChange={changeTeamsChannel}
/>
</div>
)}
</div>
) : null}
<div className="flex">
<Button variant="primary" className="mr-4" disabled={text === ''} onClick={onSubmit}>
Add Note
</Button>
<div className="flex items-center cursor-pointer" onClick={() => setPublic(!isPublic)}>
<Checkbox checked={isPublic} />
<TeamBadge />
</div>
</div>
</div>
);
}
export default connect(
(state: any) => {
const {
isVisible,
time = 0,
isEdit,
note: editNote,
} = state.getIn(['sessions', 'createNoteTooltip']);
const slackChannels = state.getIn(['slack', 'list']);
const teamsChannels = state.getIn(['teams', 'list']);
const sessionId = state.getIn(['sessions', 'current']).sessionId;
return { isVisible, time, sessionId, isEdit, editNote, slackChannels, teamsChannels };
},
{ setCreateNoteTooltip, addNote, updateNote, fetchSlack, fetchTeams }
)(CreateNote);