diff --git a/frontend/app/components/Assist/RecordingsList/RecordingsList.tsx b/frontend/app/components/Assist/RecordingsList/RecordingsList.tsx index 7f71d157d..5f9eb68f9 100644 --- a/frontend/app/components/Assist/RecordingsList/RecordingsList.tsx +++ b/frontend/app/components/Assist/RecordingsList/RecordingsList.tsx @@ -13,8 +13,8 @@ function RecordingsList() { const recordsSearch = recordingsStore.search; React.useEffect(() => { - recordingsStore.fetchRecordings() - }, []) + recordingsStore.fetchRecordings(); + }, []); React.useEffect(() => { setRecordings(filterList(recordings, recordsSearch, ['createdBy', 'name'])); diff --git a/frontend/app/components/Assist/RecordingsList/RecordsListItem.tsx b/frontend/app/components/Assist/RecordingsList/RecordsListItem.tsx index 132282fcf..e6996d1fc 100644 --- a/frontend/app/components/Assist/RecordingsList/RecordsListItem.tsx +++ b/frontend/app/components/Assist/RecordingsList/RecordsListItem.tsx @@ -1,71 +1,113 @@ import React from 'react'; import { Icon, ItemMenu } from 'UI'; -import { durationFromMs, checkForRecent, getDateFromMill } from 'App/date' -import { IRecord } from 'App/services/RecordingsService' -import { useStore } from 'App/mstore' -import { toast } from 'react-toastify' +import { durationFromMs, checkForRecent, getDateFromMill } from 'App/date'; +import { IRecord } from 'App/services/RecordingsService'; +import { useStore } from 'App/mstore'; +import { toast } from 'react-toastify'; +import cn from 'classnames'; interface Props { - record: IRecord; + record: IRecord; } function RecordsListItem(props: Props) { - const { record } = props; - const { recordingsStore } = useStore() + const { record } = props; + const { recordingsStore } = useStore(); + const [isEdit, setEdit] = React.useState(false); + const [recordingTitle, setRecordingTitle] = React.useState(record.name); + const inputRef = React.useRef(null); - const onRecordClick = () => { - recordingsStore.fetchRecordingUrl(record.recordId).then(url => { - window.open(url, "_blank"); - }) + const onRecordClick = () => { + recordingsStore.fetchRecordingUrl(record.recordId).then((url) => { + window.open(url, '_blank'); + }); + }; + + React.useEffect(() => { + if (inputRef.current) { + inputRef.current.style.width = `${record.name.length}ch`; } + }, [isEdit, inputRef.current]); - const onDelete = () => { - recordingsStore.deleteRecording(record.recordId).then(() => { - toast.success("Recording deleted") - }) - } + const onDelete = () => { + recordingsStore.deleteRecording(record.recordId).then(() => { + recordingsStore.setRecordings(recordingsStore.recordings.filter(rec => rec.recordId !== record.recordId)) + toast.success('Recording deleted'); + }); + }; - const menuItems = [ - { icon: 'trash', text: 'Delete', onClick: onDelete }, - ] - return ( -
-
-
-
-
- -
-
-
{record.name}
-
- {durationFromMs(record.duration)} -
-
-
-
-
-
-
- {record.createdBy} -
-
{checkForRecent(getDateFromMill(record.createdAt), 'LLL dd, yyyy, hh:mm a')}
-
-
-
-
- - -
Play Video
-
- -
+ const menuItems = [{ icon: 'trash', text: 'Delete', onClick: onDelete }]; + + const onSave = () => { + recordingsStore.updateRecordingName(record.recordId, recordingTitle); + setEdit(false); + }; + + return ( +
+
+
+
+
+
+
+ {isEdit ? ( + setRecordingTitle(e.target.value)} + onBlur={onSave} + onFocus={() => setEdit(true)} + /> + ) : ( +
setEdit(true)} + className={cn( + 'border-dotted border-gray-medium', + 'pt-1 w-fit -mt-2', + 'cursor-pointer select-none border-b' + )} + > + {recordingTitle} +
+ )} +
{durationFromMs(record.duration)}
+
+
- ); +
+
+
{record.createdBy}
+
+ {checkForRecent(getDateFromMill(record.createdAt), 'LLL dd, yyyy, hh:mm a')} +
+
+
+
+
+ + +
Play Video
+
+ +
+
+
+ ); } export default RecordsListItem; diff --git a/frontend/app/mstore/recordingsStore.ts b/frontend/app/mstore/recordingsStore.ts index 0f581a931..783e580d1 100644 --- a/frontend/app/mstore/recordingsStore.ts +++ b/frontend/app/mstore/recordingsStore.ts @@ -1,27 +1,31 @@ -import { makeAutoObservable } from "mobx" -import { recordingsService } from "App/services" -import { IRecord } from 'App/services/RecordingsService' +import { makeAutoObservable } from 'mobx'; +import { recordingsService } from 'App/services'; +import { IRecord } from 'App/services/RecordingsService'; export default class RecordingsStore { - recordings: IRecord[] = [] - loading: boolean + recordings: IRecord[] = []; + loading: boolean; - page = 1 - pageSize = 15 - order: 'desc' | 'asc' = 'desc' - search = '' + page = 1; + pageSize = 15; + order: 'desc' | 'asc' = 'desc'; + search = ''; // not later we will add search by user id - userId: number + userId: number; constructor() { - makeAutoObservable(this) + makeAutoObservable(this); + } + + setRecordings(records: IRecord[]) { + this.recordings = records; } updateSearch(val: string) { - this.search = val + this.search = val; } updatePage(page: number) { - this.page = page + this.page = page; } async fetchRecordings() { @@ -30,45 +34,53 @@ export default class RecordingsStore { limit: this.pageSize, order: this.order, search: this.search, - } + }; - this.loading = true + this.loading = true; try { - const recordings = await recordingsService.fetchRecordings(filter) - this.recordings = recordings; - this.fetchRecordingUrl(recordings[0].recordId) + const recordings = await recordingsService.fetchRecordings(filter); + this.setRecordings(recordings); return recordings; } catch (e) { - console.error(e) + console.error(e); } finally { - this.loading = false + this.loading = false; } } async fetchRecordingUrl(id: number): Promise { - this.loading = true + this.loading = true; try { - const recording = await recordingsService.fetchRecording(id) + const recording = await recordingsService.fetchRecording(id); return recording.URL; } catch (e) { - console.error(e) + console.error(e); } finally { - this.loading = false + this.loading = false; } } async deleteRecording(id: number) { - this.loading = true + this.loading = true; try { - const recording = await recordingsService.deleteRecording(id) - console.log(recording) - return recording + const recording = await recordingsService.deleteRecording(id); + return recording; } catch (e) { - console.error(e) + console.error(e); } finally { - this.loading = false + this.loading = false; } } - + async updateRecordingName(id: number, name: string) { + this.loading = true; + try { + const recording = await recordingsService.updateRecordingName(id, name); + return recording; + } catch (e) { + console.error(e); + } finally { + this.loading = false; + } + } } diff --git a/frontend/app/services/RecordingsService.ts b/frontend/app/services/RecordingsService.ts index 64cbe21f8..352a22e3b 100644 --- a/frontend/app/services/RecordingsService.ts +++ b/frontend/app/services/RecordingsService.ts @@ -6,87 +6,95 @@ interface RecordingData { } interface FetchFilter { - page: number - limit: number - order: 'asc' | 'desc' - search: string + page: number; + limit: number; + order: 'asc' | 'desc'; + search: string; } export interface IRecord { - createdAt: number - createdBy: string - duration: number - name: string - recordId: number - sessionId: number - userId: number - URL?: string + createdAt: number; + createdBy: string; + duration: number; + name: string; + recordId: number; + sessionId: number; + userId: number; + URL?: string; } export default class RecordingsService { private client: APIClient; - constructor(client?: APIClient) { - this.client = client ? client : new APIClient(); - } + constructor(client?: APIClient) { + this.client = client ? client : new APIClient(); + } - initClient(client?: APIClient) { - this.client = client || new APIClient(); - } + initClient(client?: APIClient) { + this.client = client || new APIClient(); + } - reserveUrl(siteId: string, recordingData: RecordingData): Promise { - return this.client.put(`/${siteId}/assist/save`, recordingData) - .then(r => { - if (r.ok) { - return r.json().then(j => j.data.URL) - } else { - throw new Error("Can't reserve space for recording: " + r.status); - } - }) - } + reserveUrl(siteId: string, recordingData: RecordingData): Promise { + return this.client.put(`/${siteId}/assist/save`, recordingData).then((r) => { + if (r.ok) { + return r.json().then((j) => j.data.URL); + } else { + throw new Error("Can't reserve space for recording: " + r.status); + } + }); + } - saveFile(url: string, file: Blob) { - return fetch(url, { method: 'PUT', headers: { 'Content-Type': 'video/webm' }, body: file }) - .then(r => { - if (r.ok) { - return true - } else { - throw new Error("Can't upload file: " + r.status) - } - }) - } + saveFile(url: string, file: Blob) { + return fetch(url, { + method: 'PUT', + headers: { 'Content-Type': 'video/webm' }, + body: file, + }).then((r) => { + if (r.ok) { + return true; + } else { + throw new Error("Can't upload file: " + r.status); + } + }); + } - fetchRecordings(filters: FetchFilter): Promise { - return this.client.post(`/assist/records`, filters) - .then(r => { - if (r.ok) { - return r.json().then(j => j.data) - } else { - throw new Error("Can't get recordings: " + r.status); - } - }) - } + fetchRecordings(filters: FetchFilter): Promise { + return this.client.post(`/assist/records`, filters).then((r) => { + if (r.ok) { + return r.json().then((j) => j.data); + } else { + throw new Error("Can't get recordings: " + r.status); + } + }); + } - fetchRecording(id: number): Promise { - return this.client.get(`/assist/records/${id}`) - .then(r => { - if (r.ok) { - return r.json().then(j => j.data) - } else { - throw new Error("Can't get recordings: " + r.status); - } - }) - } + fetchRecording(id: number): Promise { + return this.client.get(`/assist/records/${id}`).then((r) => { + if (r.ok) { + return r.json().then((j) => j.data); + } else { + throw new Error("Can't get recordings: " + r.status); + } + }); + } - deleteRecording(id: number): Promise { - return this.client.delete(`/assist/records/${id}`) - .then(r => { - if (r.ok) { - return r.json().then(j => j.data) - } else { - throw new Error("Can't get recordings: " + r.status); - } - }) - } + updateRecordingName(id: number, name: string): Promise { + return this.client.post(`/assist/records/${id}`, { name }).then((r) => { + if (r.ok) { + return r.json().then((j) => j.data); + } else { + throw new Error("Can't get recordings: " + r.status); + } + }); + } + deleteRecording(id: number): Promise { + return this.client.delete(`/assist/records/${id}`).then((r) => { + if (r.ok) { + return r.json().then((j) => j.data); + } else { + throw new Error("Can't get recordings: " + r.status); + } + }); + } }