@@ -168,6 +172,7 @@ export interface UserMetasTableProps {
}
export function UserMetasTable({ rows, reloadRows }: UserMetasTableProps): React.JSX.Element {
+ const { t } = useTranslation(['user_metas']);
const { deselectAll, deselectOne, selectAll, selectOne, selected } = useUserMetasSelection();
const [idToDelete, setIdToDelete] = React.useState('');
@@ -207,7 +212,7 @@ export function UserMetasTable({ rows, reloadRows }: UserMetasTableProps): React
sx={{ textAlign: 'center' }}
variant="body2"
>
- No user metadata found
+ {t('no-user-meta-found')}
) : null}
diff --git a/002_source/cms/src/components/dashboard/vocabulary/_constants.ts b/002_source/cms/src/components/dashboard/vocabulary/_constants.ts
index a7cba1d..3fb3d67 100644
--- a/002_source/cms/src/components/dashboard/vocabulary/_constants.ts
+++ b/002_source/cms/src/components/dashboard/vocabulary/_constants.ts
@@ -1,5 +1,4 @@
-import { dayjs } from '@/lib/dayjs';
-import { Vocabulary, CreateForm } from './type';
+import type { CreateForm, Vocabulary } from './type';
export const defaultVocabulary: Vocabulary = {
id: 'default-vocabulary-id',
diff --git a/002_source/cms/src/components/dashboard/vocabulary/confirm-delete-modal.tsx b/002_source/cms/src/components/dashboard/vocabulary/confirm-delete-modal.tsx
index 671b235..7a391d6 100644
--- a/002_source/cms/src/components/dashboard/vocabulary/confirm-delete-modal.tsx
+++ b/002_source/cms/src/components/dashboard/vocabulary/confirm-delete-modal.tsx
@@ -1,8 +1,7 @@
'use client';
import * as React from 'react';
-import { useRouter } from 'next/navigation';
-import { COL_LESSON_TYPES } from '@/constants';
+import deleteVocabulary from '@/db/Vocabularies/Delete';
import { LoadingButton } from '@mui/lab';
import { Button, Container, Modal, Paper } from '@mui/material';
import Avatar from '@mui/material/Avatar';
@@ -10,14 +9,15 @@ import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { Note as NoteIcon } from '@phosphor-icons/react/dist/ssr/Note';
-import PocketBase from 'pocketbase';
+// TODO: remove import
+// import PocketBase from 'pocketbase';
import { useTranslation } from 'react-i18next';
import { logger } from '@/lib/default-logger';
import { toast } from '@/components/core/toaster';
-import deleteVocabulary from '@/db/Vocabularies/Delete';
-const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL);
+// TODO: remove pb
+// const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL);
export default function ConfirmDeleteModal({
open,
@@ -110,7 +110,7 @@ export default function ConfirmDeleteModal({
{
+ onClick={() => {
handleUserConfirmDelete();
}}
loading={isDeleteing}
diff --git a/002_source/cms/src/components/dashboard/vocabulary/type.d.ts b/002_source/cms/src/components/dashboard/vocabulary/type.d.ts
index 1506e8e..e309b4a 100644
--- a/002_source/cms/src/components/dashboard/vocabulary/type.d.ts
+++ b/002_source/cms/src/components/dashboard/vocabulary/type.d.ts
@@ -1,10 +1,9 @@
// RULES:
// should match the collection `Vocabularies` from `schema.dbml`
export interface Vocabulary {
- id: string;
created?: string;
updated?: string;
- image?: string;
+ image: string;
sound?: string;
word?: string;
word_c?: string;
@@ -23,6 +22,9 @@ export interface Vocabulary {
//
};
};
+ //
+ id: string;
+ collectionId: string;
}
// RULES: for use with vocabulary-create-form.tsx
diff --git a/002_source/cms/src/components/dashboard/vocabulary/vocabularies-filters.tsx b/002_source/cms/src/components/dashboard/vocabulary/vocabularies-filters.tsx
index 83c8354..74e61fc 100644
--- a/002_source/cms/src/components/dashboard/vocabulary/vocabularies-filters.tsx
+++ b/002_source/cms/src/components/dashboard/vocabulary/vocabularies-filters.tsx
@@ -2,9 +2,12 @@
import * as React from 'react';
import { useRouter } from 'next/navigation';
+import { listLessonCategories } from '@/db/LessonCategories/ListLessonCategories';
import GetAllCount from '@/db/Vocabularies/GetAllCount';
import GetHiddenCount from '@/db/Vocabularies/GetHiddenCount';
import GetVisibleCount from '@/db/Vocabularies/GetVisibleCount';
+// import { listLessonCategories } from '@/db/LessonCategories/ListLessonCategories';
+import { MenuItem } from '@mui/material';
import Button from '@mui/material/Button';
import Chip from '@mui/material/Chip';
import Divider from '@mui/material/Divider';
@@ -19,14 +22,12 @@ import Typography from '@mui/material/Typography';
import { useTranslation } from 'react-i18next';
import { paths } from '@/paths';
+import { logger } from '@/lib/default-logger';
import { FilterButton, FilterPopover, useFilterContext } from '@/components/core/filter-button';
import { Option } from '@/components/core/option';
-import { useVocabulariesSelection } from './vocabularies-selection-context';
import type { Vocabulary } from './type';
-import { listLessonCategories } from '@/db/LessonCategories/listLessonCategories';
-import { MenuItem } from '@mui/material';
-import { logger } from '@/lib/default-logger';
+import { useVocabulariesSelection } from './vocabularies-selection-context';
export interface Filters {
email?: string;
@@ -50,6 +51,7 @@ export interface VocabulariesFiltersProps {
export function VocabulariesFilters({
filters = {},
sortDir = 'desc',
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
fullData,
}: VocabulariesFiltersProps): React.JSX.Element {
const { t } = useTranslation();
@@ -119,12 +121,13 @@ export function VocabulariesFilters({
updateSearchParams({}, sortDir);
}, [updateSearchParams, sortDir]);
- const handleStatusChange = React.useCallback(
- (_: React.SyntheticEvent, value: string) => {
- updateSearchParams({ ...filters, status: value }, sortDir);
- },
- [updateSearchParams, filters, sortDir]
- );
+ // TODO: remove me
+ // const handleStatusChange = React.useCallback(
+ // (_: React.SyntheticEvent, value: string) => {
+ // updateSearchParams({ ...filters, status: value }, sortDir);
+ // },
+ // [updateSearchParams, filters, sortDir]
+ // );
const handleVisibleChange = React.useCallback(
(_: React.SyntheticEvent, value: string) => {
@@ -161,19 +164,19 @@ export function VocabulariesFilters({
[updateSearchParams, filters, sortDir]
);
- const handleEmailChange = React.useCallback(
- (value?: string) => {
- updateSearchParams({ ...filters, email: value }, sortDir);
- },
- [updateSearchParams, filters, sortDir]
- );
+ // const handleEmailChange = React.useCallback(
+ // (value?: string) => {
+ // updateSearchParams({ ...filters, email: value }, sortDir);
+ // },
+ // [updateSearchParams, filters, sortDir]
+ // );
- const handlePhoneChange = React.useCallback(
- (value?: string) => {
- updateSearchParams({ ...filters, phone: value }, sortDir);
- },
- [updateSearchParams, filters, sortDir]
- );
+ // const handlePhoneChange = React.useCallback(
+ // (value?: string) => {
+ // updateSearchParams({ ...filters, phone: value }, sortDir);
+ // },
+ // [updateSearchParams, filters, sortDir]
+ // );
const handleSortChange = React.useCallback(
(event: SelectChangeEvent) => {
@@ -185,7 +188,7 @@ export function VocabulariesFilters({
const [allCategories, setAllCategories] = React.useState<{ value: string; label: string }[]>([]);
async function listAllCategories() {
try {
- let result = await listLessonCategories();
+ const result = await listLessonCategories();
const tempAllCategories = result.map((c) => {
return {
value: c.id,
@@ -568,42 +571,43 @@ function EmailFilterPopover(): React.JSX.Element {
);
}
-function PhoneFilterPopover(): React.JSX.Element {
- const { anchorEl, onApply, onClose, open, value: initialValue } = useFilterContext();
- const [value, setValue] = React.useState('');
+// remove PhoneFilterPopover
+// function PhoneFilterPopover(): React.JSX.Element {
+// const { anchorEl, onApply, onClose, open, value: initialValue } = useFilterContext();
+// const [value, setValue] = React.useState('');
- React.useEffect(() => {
- setValue((initialValue as string | undefined) ?? '');
- }, [initialValue]);
+// React.useEffect(() => {
+// setValue((initialValue as string | undefined) ?? '');
+// }, [initialValue]);
- return (
-
-
- {
- setValue(event.target.value);
- }}
- onKeyUp={(event) => {
- if (event.key === 'Enter') {
- onApply(value);
- }
- }}
- value={value}
- />
-
-
-
- );
-}
+// return (
+//
+//
+// {
+// setValue(event.target.value);
+// }}
+// onKeyUp={(event) => {
+// if (event.key === 'Enter') {
+// onApply(value);
+// }
+// }}
+// value={value}
+// />
+//
+//
+//
+// );
+// }
diff --git a/002_source/cms/src/components/dashboard/vocabulary/vocabularies-pagination.tsx b/002_source/cms/src/components/dashboard/vocabulary/vocabularies-pagination.tsx
index b7256e4..1dcf2f6 100644
--- a/002_source/cms/src/components/dashboard/vocabulary/vocabularies-pagination.tsx
+++ b/002_source/cms/src/components/dashboard/vocabulary/vocabularies-pagination.tsx
@@ -6,9 +6,10 @@
import * as React from 'react';
import TablePagination from '@mui/material/TablePagination';
-function noop(): void {
- return undefined;
-}
+// TODO: remove noop
+// function noop(): void {
+// return undefined;
+// }
interface VocabulariesPaginationProps {
count: number;
@@ -29,11 +30,11 @@ export function VocabulariesPagination({
}: VocabulariesPaginationProps): React.JSX.Element {
// You should implement the pagination using a similar logic as the filters.
// Note that when page change, you should keep the filter search params.
- const handleChangePage = (event: unknown, newPage: number) => {
+ const handleChangePage = (event: unknown, newPage: number): void => {
setPage(newPage);
};
- const handleChangeRowsPerPage = (event: React.ChangeEvent) => {
+ const handleChangeRowsPerPage = (event: React.ChangeEvent): void => {
setRowsPerPage(parseInt(event.target.value));
// console.log(parseInt(event.target.value));
};
diff --git a/002_source/cms/src/components/dashboard/vocabulary/vocabularies-table.tsx b/002_source/cms/src/components/dashboard/vocabulary/vocabularies-table.tsx
index 44ac46d..9161d28 100644
--- a/002_source/cms/src/components/dashboard/vocabulary/vocabularies-table.tsx
+++ b/002_source/cms/src/components/dashboard/vocabulary/vocabularies-table.tsx
@@ -16,12 +16,13 @@ import { useTranslation } from 'react-i18next';
import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs';
+import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import { DataTable } from '@/components/core/data-table';
import type { ColumnDef } from '@/components/core/data-table';
import ConfirmDeleteModal from './confirm-delete-modal';
-import { useVocabulariesSelection } from './vocabularies-selection-context';
import type { Vocabulary } from './type';
+import { useVocabulariesSelection } from './vocabularies-selection-context';
function columns(handleDeleteClick: (testId: string) => void): ColumnDef[] {
return [
@@ -45,7 +46,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef
@@ -104,7 +105,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef {
setShowLoading(true);
diff --git a/002_source/cms/src/components/dashboard/vocabulary/vocabulary-edit-form.tsx b/002_source/cms/src/components/dashboard/vocabulary/vocabulary-edit-form.tsx
index f67992b..fafb941 100644
--- a/002_source/cms/src/components/dashboard/vocabulary/vocabulary-edit-form.tsx
+++ b/002_source/cms/src/components/dashboard/vocabulary/vocabulary-edit-form.tsx
@@ -4,6 +4,7 @@ import * as React from 'react';
import RouterLink from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import { COL_VOCABULARIES } from '@/constants';
+import { listLessonCategories } from '@/db/LessonCategories/ListLessonCategories';
import { zodResolver } from '@hookform/resolvers/zod';
import { LoadingButton } from '@mui/lab';
import { Avatar, Divider } from '@mui/material';
@@ -12,7 +13,6 @@ import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardActions from '@mui/material/CardActions';
import CardContent from '@mui/material/CardContent';
-
import FormControl from '@mui/material/FormControl';
import FormHelperText from '@mui/material/FormHelperText';
import InputLabel from '@mui/material/InputLabel';
@@ -27,8 +27,10 @@ import { useTranslation } from 'react-i18next';
import { z as zod } from 'zod';
import { paths } from '@/paths';
+import isDevelopment from '@/lib/check-is-development';
import { logger } from '@/lib/default-logger';
import { base64ToFile, fileToBase64 } from '@/lib/file-to-base64';
+import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import { pb } from '@/lib/pb';
import { Option } from '@/components/core/option';
import { toast } from '@/components/core/toaster';
@@ -36,36 +38,6 @@ import FormLoading from '@/components/loading';
import ErrorDisplay from '../error';
import type { EditFormProps } from './type';
-import { listLessonCategories } from '@/db/LessonCategories/listLessonCategories';
-import isDevelopment from '@/lib/check-is-development';
-
-const schema = zod.object({
- image: zod.union([zod.array(zod.any()), zod.string()]).optional(),
- sound: zod.union([zod.array(zod.any()), zod.string()]).optional(),
- word: zod.string().min(1, 'Word is required').max(255),
- word_c: zod.string().min(1, 'Chinese word is required').max(255),
- sample_e: zod.string().optional(),
- sample_c: zod.string().optional(),
- cat_id: zod.string().min(1, 'Category ID is required'),
- category: zod.string().optional(),
- lesson_type_id: zod.string().optional(),
- // NOTE: for image handling
- avatar: zod.string().optional(),
-});
-
-type Values = zod.infer;
-
-const defaultValues = {
- image: undefined,
- sound: undefined,
- word: '',
- word_c: '',
- sample_e: '',
- sample_c: '',
- cat_id: '',
- category: '',
- lesson_type_id: '',
-} satisfies Values;
export function VocabularyEditForm(): React.JSX.Element {
const router = useRouter();
@@ -78,6 +50,36 @@ export function VocabularyEditForm(): React.JSX.Element {
//
const [showError, setShowError] = React.useState({ show: false, detail: '' });
+ const schema = zod.object({
+ image: zod.union([zod.array(zod.any()), zod.string()]).optional(),
+ sound: zod.union([zod.array(zod.any()), zod.string()]).optional(),
+ word: zod.string().min(1, 'Word is required').max(255),
+ word_c: zod.string().min(1, 'Chinese word is required').max(255),
+ sample_e: zod.string().optional(),
+ sample_c: zod.string().optional(),
+ cat_id: zod.string().min(1, 'Category ID is required'),
+ category: zod.string().optional(),
+ lesson_type_id: zod.string().optional(),
+ // NOTE: for image handling
+ avatar: zod.string().optional(),
+ visible: zod.string(),
+ });
+
+ type Values = zod.infer;
+
+ const defaultValues = {
+ image: undefined,
+ sound: undefined,
+ word: '',
+ word_c: '',
+ sample_e: '',
+ sample_c: '',
+ cat_id: '',
+ category: '',
+ lesson_type_id: '',
+ visible: 'visible',
+ } satisfies Values;
+
const {
control,
handleSubmit,
@@ -144,9 +146,7 @@ export function VocabularyEditForm(): React.JSX.Element {
reset({ ...defaultValues, ...result });
if (result.image !== '') {
- const fetchResult = await fetch(
- `http://127.0.0.1:8090/api/files/${result.collectionId}/${result.id}/${result.image}`
- );
+ const fetchResult = await fetch(getImageUrlFromFile(result.collectionId, result.id, result.image));
const blob = await fetchResult.blob();
const url = await fileToBase64(blob);
diff --git a/002_source/cms/src/components/loading/index.tsx b/002_source/cms/src/components/loading/index.tsx
index 219a67e..8d5d23d 100644
--- a/002_source/cms/src/components/loading/index.tsx
+++ b/002_source/cms/src/components/loading/index.tsx
@@ -19,15 +19,16 @@ function Loading(): React.JSX.Element {
alignItems: 'center',
}}
>
-
+
{t('loading')}
);
}
export default function FormLoading(): React.JSX.Element {
- const { t } = useTranslation();
-
return (