Skip to content

Commit

Permalink
implemented rename functionality for attachment (#1063)
Browse files Browse the repository at this point in the history
* implemented rename functionality for attachment

* added error message for validation

* fix
  • Loading branch information
Tolik170 authored Sep 8, 2023
1 parent de9114a commit 372c1cf
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ export const styles = {
fontSize: '12px',
typography: TypographyVariantEnum.Caption
},
inputWithIcons: {
display: 'flex',
columnGap: '10px',
flex: 1
},
input: {
flex: 1,
typography: TypographyVariantEnum.Subtitle2
},
actionIcon: (color: string) => ({
width: '16px',
height: '16px',
p: '4px',
borderRadius: '6px',
color: `${color}.700`,
backgroundColor: `${color}.100`
}),
titleWithDescription: {
title: {
typography: TypographyVariantEnum.Subtitle2
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,94 @@
import { FC } from 'react'
import { FC, useState, useRef, useEffect, ChangeEvent } from 'react'
import { useTranslation } from 'react-i18next'

import IconButton from '@mui/material/IconButton'
import ClearIcon from '@mui/icons-material/Clear'
import DoneIcon from '@mui/icons-material/Done'
import InputBase from '@mui/material/InputBase'
import Box from '@mui/material/Box'

import TitleWithDescription from '~/components/title-with-description/TitleWithDescription'
import { styles } from '~/components/icon-extension-with-title/IconExtensionWithTitle.styles'

import { snackbarVariants } from '~/constants'
import { convertBytesToProperFormat } from '~/utils/helper-functions'
import { styles } from '~/components/icon-extension-with-title/IconExtensionWithTitle.styles'

interface IconExtensionWithTitleProps {
title: string
size?: number
isEditable?: boolean
onCancel?: () => void
onSave?: (fileName: string) => Promise<void>
}

const IconExtensionWithTitle: FC<IconExtensionWithTitleProps> = ({
title,
size
size,
isEditable = false,
onCancel,
onSave
}) => {
const { t } = useTranslation()
const inputRef = useRef<HTMLInputElement | null>(null)
const initialInputValue = title.substring(0, title.lastIndexOf('.'))
const [inputValue, setInputValue] = useState<string>(initialInputValue)
const [fileExtension] = title.split('.').reverse()

const fileExtension = title.slice(title.lastIndexOf('.') + 1)
const convertSize = (incomingSize: number) => {
const { size: properSize, unit } = convertBytesToProperFormat(incomingSize)
return properSize + ' ' + t(`common.${unit}`)
}

const onChange = (e: ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value)
}

const onCancelHandler = () => {
onCancel?.()
}

const onSaveHandler = () => {
const trimmedValue = inputValue.trim()
const value = trimmedValue !== initialInputValue ? trimmedValue : ''
onSave && void onSave(value)
}

useEffect(() => {
inputRef?.current?.select()
!isEditable && setInputValue(initialInputValue)
}, [isEditable, initialInputValue])

const actionIcons = (
<Box>
<IconButton onClick={onCancelHandler}>
<ClearIcon sx={styles.actionIcon(snackbarVariants.error)} />
</IconButton>
<IconButton onClick={onSaveHandler}>
<DoneIcon sx={styles.actionIcon(snackbarVariants.success)} />
</IconButton>
</Box>
)

return (
<Box sx={styles.container}>
<Box sx={styles.iconBox}>{fileExtension}</Box>
<TitleWithDescription
description={size && convertSize(size)}
style={styles.titleWithDescription}
title={title}
/>
{isEditable ? (
<Box sx={styles.inputWithIcons}>
<InputBase
inputRef={inputRef}
onChange={onChange}
sx={styles.input}
value={inputValue}
/>
{actionIcons}
</Box>
) : (
<TitleWithDescription
description={size && convertSize(size)}
style={styles.titleWithDescription}
title={title}
/>
)}
</Box>
)
}
Expand Down
1 change: 1 addition & 0 deletions src/constants/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const URLs = {
},
attachments: {
get: '/attachments',
patch: '/attachments',
delete: '/attachments'
}
},
Expand Down
1 change: 1 addition & 0 deletions src/constants/translations/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"search": "Search",
"edit": "Edit",
"delete": "Delete",
"rename": "Rename",
"showMore": "Show more",
"showLess": "Show less",
"confirmButton": "OK",
Expand Down
3 changes: 2 additions & 1 deletion src/constants/translations/en/my-resources-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"lastUpdate": "Last update",
"emptyAttachments": "You have no attachments yet",
"confirmAttachmentDeletionTitle":"Do you confirm attachment deletion?",
"successDeletion":"Attachment was deleted successfully"
"successDeletion":"Attachment was deleted successfully",
"validationError": "File name cannot be longer than 55 symbol."
},
"quizzes": {
"newQuizBtn": "New quiz",
Expand Down
2 changes: 2 additions & 0 deletions src/constants/translations/ua/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"of": "З",
"search": "Пошук",
"edit":"Редагувати",
"delete": "Видалити",
"rename": "Перейменувати",
"showMore": "Показати більше",
"showLess": "Показати менше",
"uah": "ГРН",
Expand Down
3 changes: 2 additions & 1 deletion src/constants/translations/ua/my-resources-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"emptyAttachments": "У вас ще немає вкладень",
"confirmAttachmentDeletionTitle":"Ви підтверджуєте видалення вкладення?",
"successDeletion":"Вкладення було успішно видалено",
"addAttachment": "Добавити файл"
"addAttachment": "Добавити файл",
"validationError": "Назва файлу не може бути довшою за 55 символів."
},
"quizzes": {
"newQuizBtn": "Новий тест",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,22 @@ import {
} from '~/types'
import { styles } from '~/containers/my-resources/attachments-container/AttachmentsContainer.styles'

export const columns: TableColumn<Attachment>[] = [
export const columns = (
selectedItemId: string,
onCancel: () => void,
onSave: (fileName: string) => Promise<void>
): TableColumn<Attachment>[] => [
{
label: 'myResourcesPage.attachments.file',
field: 'fileName',
calculatedCellValue: (item: Attachment) => {
return <IconExtensionWithTitle title={item.fileName} />
}
calculatedCellValue: (item: Attachment) => (
<IconExtensionWithTitle
isEditable={selectedItemId === item._id}
onCancel={onCancel}
onSave={onSave}
title={item.fileName}
/>
)
},
{
label: 'myResourcesPage.attachments.size',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ export const styles = {
},
addAttachmentBtn: { width: 'fit-content' },
addAttachmentIcon: { ml: '5px', width: { xs: '18px', sm: '22px' } },
table: roundedBorderTable,
table: {
...roundedBorderTable,
'& td,th': {
'&:first-of-type': { maxWidth: '50%', width: '100%' }
}
},
sizeTitle: captionTitle,
dateTitle: captionTitle
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useRef } from 'react'
import { useCallback, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Box from '@mui/material/Box'

Expand All @@ -15,52 +15,48 @@ import { useSnackBarContext } from '~/context/snackbar-context'
import { ResourceService } from '~/services/resource-service'
import { authRoutes } from '~/router/constants/authRoutes'

import { defaultResponses, snackbarVariants } from '~/constants'
import {
columns,
initialSort,
itemsLoadLimit,
removeColumnRules
} from '~/containers/my-resources/attachments-container/AttachmentsContainer.constants'
import { ajustColumns, getScreenBasedLimit } from '~/utils/helper-functions'
import { ItemsWithCount, Attachment, ErrorResponse } from '~/types'
import { defaultResponses, snackbarVariants } from '~/constants'
import {
ItemsWithCount,
Attachment,
ErrorResponse,
UpdateAttachmentParams
} from '~/types'
import { styles } from '~/containers/my-resources/attachments-container/AttachmentsContainer.styles'

const AttachmentsContainer = () => {
const { t } = useTranslation()
const { setAlert } = useSnackBarContext()
const { openDialog } = useConfirm()
const { page, handleChangePage } = usePagination()

const sortOptions = useSort({ initialSort })
const { sort } = sortOptions

const breakpoints = useBreakpoints()
const itemsPerPage = getScreenBasedLimit(breakpoints, itemsLoadLimit)

const sortOptions = useSort({ initialSort })
const searchFileName = useRef<string>('')
const [selectedItemId, setSelectedItemId] = useState<string>('')

const getAttachments = useCallback(
() =>
ResourceService.getAttachments({
limit: itemsPerPage,
skip: (page - 1) * itemsPerPage,
sort,
fileName: searchFileName.current
}),
[itemsPerPage, page, sort, searchFileName]
)
const { sort } = sortOptions
const itemsPerPage = getScreenBasedLimit(breakpoints, itemsLoadLimit)

const onAttachmentError = useCallback(
(error: ErrorResponse) => {
(error: ErrorResponse, message?: string) => {
setAlert({
severity: snackbarVariants.error,
message: error ? `errors.${error.code}` : ''
message: error ? message ?? `errors.${error.code}` : ''
})
},
[setAlert]
)

const onAttachmentUpdateError = (error: ErrorResponse) =>
onAttachmentError(error, 'myResourcesPage.attachments.validationError')

const onDeleteAttachmentError = (error: ErrorResponse) => {
setAlert({
severity: snackbarVariants.error,
Expand All @@ -75,11 +71,28 @@ const AttachmentsContainer = () => {
})
}

const getAttachments = useCallback(
() =>
ResourceService.getAttachments({
limit: itemsPerPage,
skip: (page - 1) * itemsPerPage,
sort,
fileName: searchFileName.current
}),
[itemsPerPage, page, sort, searchFileName]
)

const deleteAttachment = useCallback(
(id?: string) => ResourceService.deleteAttachment(id ?? ''),
[]
)

const updateAttachment = useCallback(
(params?: UpdateAttachmentParams) =>
ResourceService.updateAttachment(params),
[]
)

const {
response,
loading,
Expand All @@ -90,6 +103,19 @@ const AttachmentsContainer = () => {
onResponseError: onAttachmentError
})

const onAttachmentUpdate = useCallback(
() => void fetchGetAttachments(),
[fetchGetAttachments]
)

const { fetchData: updateData } = useAxios({
service: updateAttachment,
defaultResponse: null,
onResponseError: onAttachmentUpdateError,
onResponse: onAttachmentUpdate,
fetchOnMount: false
})

const { error, fetchData: fetchDeleteAttachment } = useAxios({
service: deleteAttachment,
fetchOnMount: false,
Expand All @@ -98,6 +124,13 @@ const AttachmentsContainer = () => {
onResponse: onDeleteAttachmentResponse
})

const onSave = async (fileName: string) => {
if (fileName) await updateData({ id: selectedItemId, fileName })
setSelectedItemId('')
}
const onEdit = (id: string) => setSelectedItemId(id)
const onCancel = () => setSelectedItemId('')

const handleDeleteAttachment = async (id: string, isConfirmed: boolean) => {
if (isConfirmed) {
await fetchDeleteAttachment(id)
Expand All @@ -114,11 +147,16 @@ const AttachmentsContainer = () => {
})
}

const columnsToShow = ajustColumns(breakpoints, columns, removeColumnRules)
const columnsToShow = ajustColumns(
breakpoints,
columns(selectedItemId, onCancel, onSave),
removeColumnRules
)

const rowActions = [
{
label: t('common.edit'),
func: () => console.log(t('common.edit'))
label: t('common.rename'),
func: onEdit
},
{
label: t('common.delete'),
Expand Down
8 changes: 7 additions & 1 deletion src/services/resource-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
GetLessonsParams,
ItemsWithCount,
LessonData,
Lesson
Lesson,
UpdateAttachmentParams
} from '~/types'
import { createUrlPath } from '~/utils/helper-functions'

Expand All @@ -33,6 +34,11 @@ export const ResourceService = {
params?: Partial<GetAttachmentsParams>
): Promise<AxiosResponse<ItemsWithCount<Attachment>>> =>
await axiosClient.get(URLs.resources.attachments.get, { params }),
updateAttachment: async (params?: UpdateAttachmentParams) =>
await axiosClient.patch(
createUrlPath(URLs.resources.attachments.patch, params?.id),
params
),
deleteAttachment: async (id: string): Promise<AxiosResponse> =>
await axiosClient.delete(
createUrlPath(URLs.resources.attachments.delete, id)
Expand Down
Loading

0 comments on commit 372c1cf

Please sign in to comment.