Files
lettersoup-online/002_source/cms/src/app/dashboard/mf/_repomix.md
louiscklaw da08798b10 update,
2025-04-24 01:08:44 +08:00

96 KiB

This file is a merged representation of the entire codebase, combined into a single document by Repomix.

File Summary

Purpose

This file contains a packed representation of the entire repository's contents. It is designed to be easily consumable by AI systems for analysis, code review, or other automated processes.

File Format

The content is organized as follows:

  1. This summary section
  2. Repository information
  3. Directory structure
  4. Multiple file entries, each consisting of: a. A header with the file path (## File: path/to/file) b. The full contents of the file in a code block

Usage Guidelines

  • This file should be treated as read-only. Any changes should be made to the original repository files, not this packed version.
  • When processing this file, use the file path to distinguish between different files in the repository.
  • Be aware that this file may contain sensitive information. Handle it with the same level of security as you would the original repository.

Notes

  • Some files may have been excluded based on .gitignore rules and Repomix's configuration
  • Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files
  • Files matching patterns in .gitignore are excluded
  • Files matching default ignore patterns are excluded
  • Files are sorted by Git change count (files with more changes are at the bottom)

Additional Info

Directory Structure

categories/
  [cat_id]/
    BasicDetailCard.tsx
    page.tsx
    TitleCard.tsx
  create/
    page.tsx
  edit/
    [cat_id]/
      _PROMPT.md
      page.tsx
  lp-categories-sample-data.tsx
  page.tsx
questions/
  [cat_id]/
    BasicDetailCard.tsx
    page.tsx
    TitleCard.tsx
  create/
    page.tsx
  edit/
    [cat_id]/
      _PROMPT.md
      page.tsx
  lp-categories-sample-data.tsx
  page.tsx
repomix-output.xml

Files

File: categories/[cat_id]/BasicDetailCard.tsx

'use client';

import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import Card from '@mui/material/Card';
import CardHeader from '@mui/material/CardHeader';
import Chip from '@mui/material/Chip';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
import { useTranslation } from 'react-i18next';

import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list';
import type { MfCategory } from '@/components/dashboard/mf/categories/type';

export default function BasicDetailCard({
  lpModel: model,
  handleEditClick,
}: {
  lpModel: MfCategory;
  handleEditClick: () => void;
}): React.JSX.Element {
  const { t } = useTranslation();

  return (
    <Card>
      <CardHeader
        action={
          <IconButton
            onClick={() => {
              handleEditClick();
            }}
          >
            <PencilSimpleIcon />
          </IconButton>
        }
        avatar={
          <Avatar>
            <UserIcon fontSize="var(--Icon-fontSize)" />
          </Avatar>
        }
        title={t('list.basic-details')}
      />
      <PropertyList
        divider={<Divider />}
        orientation="vertical"
        sx={{ '--PropertyItem-padding': '12px 24px' }}
      >
        {(
          [
            {
              key: 'Customer ID',
              value: (
                <Chip
                  label={model.id}
                  size="small"
                  variant="soft"
                />
              ),
            },
            { key: 'Name', value: model.cat_name },
            { key: 'Remarks', value: model.remarks },
            { key: 'Description', value: model.description },
          ] satisfies { key: string; value: React.ReactNode }[]
        ).map(
          (item): React.JSX.Element => (
            <PropertyItem
              key={item.key}
              name={item.key}
              value={item.value}
            />
          )
        )}
      </PropertyList>
    </Card>
  );
}

File: categories/[cat_id]/page.tsx

'use client';

import * as React from 'react';
import RouterLink from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import SampleAddressCard from '@/app/dashboard/Sample/AddressCard';
import { SampleNotifications } from '@/app/dashboard/Sample/Notifications';
import SamplePaymentCard from '@/app/dashboard/Sample/PaymentCard';
import SampleSecurityCard from '@/app/dashboard/Sample/SecurityCard';
import { COL_QUIZ_MF_CATEGORIES } from '@/constants';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Grid from '@mui/material/Unstable_Grid2';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import type { RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error';
import { defaultMfCategory } from '@/components/dashboard/mf/categories/_constants.ts';
import { Notifications } from '@/components/dashboard/mf/categories/notifications';
import type { MfCategory } from '@/components/dashboard/mf/categories/type';
import FormLoading from '@/components/loading';

import BasicDetailCard from './BasicDetailCard';
import TitleCard from './TitleCard';

export default function Page(): React.JSX.Element {
  const { t } = useTranslation();
  const router = useRouter();
  //
  const { cat_id: catId } = useParams<{ cat_id: string }>();
  //
  const [showLoading, setShowLoading] = React.useState<boolean>(true);
  const [showError, setShowError] = React.useState({ show: false, detail: '' });

  //
  const [showLessonCategory, setShowLessonCategory] = React.useState<MfCategory>(defaultMfCategory);

  function handleEditClick() {
    router.push(paths.dashboard.mf_categories.edit(showLessonCategory.id));
  }

  React.useEffect(() => {
    if (catId) {
      pb.collection(COL_QUIZ_MF_CATEGORIES)
        .getOne(catId)
        .then((model: RecordModel) => {
          setShowLessonCategory({ ...defaultMfCategory, ...model });
        })
        .catch((err) => {
          logger.error(err);
          toast(t('list.error'));

          setShowError({ show: true, detail: JSON.stringify(err) });
        })
        .finally(() => {
          setShowLoading(false);
        });
    }
  }, [catId]);

  if (showLoading) return <FormLoading />;
  if (showError.show)
    return (
      <ErrorDisplay
        message={t('error.unable-to-process-request')}
        code="500"
        details={showError.detail}
      />
    );

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack spacing={3}>
          <div>
            <Link
              color="text.primary"
              component={RouterLink}
              href={paths.dashboard.mf_categories.list}
              sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
              variant="subtitle2"
            >
              <ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
              {t('list.title')}
            </Link>
          </div>
          <Stack
            direction={{ xs: 'column', sm: 'row' }}
            spacing={3}
            sx={{ alignItems: 'flex-start' }}
          >
            <TitleCard lpModel={showLessonCategory} />
          </Stack>
        </Stack>
        <Grid
          container
          spacing={4}
        >
          <Grid
            lg={4}
            xs={12}
          >
            <Stack spacing={4}>
              <BasicDetailCard
                lpModel={showLessonCategory}
                handleEditClick={handleEditClick}
              />
              <SampleSecurityCard />
            </Stack>
          </Grid>
          <Grid
            lg={8}
            xs={12}
          >
            <Stack spacing={4}>
              <SamplePaymentCard />
              <SampleAddressCard />
              <Notifications notifications={SampleNotifications} />
            </Stack>
          </Grid>
        </Grid>
      </Stack>
    </Box>
  );
}

File: categories/[cat_id]/TitleCard.tsx

'use client';

import * as React from 'react';
import { Button } from '@mui/material';
import Avatar from '@mui/material/Avatar';
import Chip from '@mui/material/Chip';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next';

import type { MfCategory } from '@/components/dashboard/mf/categories/type';

function getImageUrlFrRecord(record: MfCategory): string {
  return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}

export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): React.JSX.Element {
  const { t } = useTranslation();

  return (
    <>
      <Stack
        direction="row"
        spacing={2}
        sx={{ alignItems: 'center', flex: '1 1 auto' }}
      >
        <Avatar
          variant="rounded"
          src={getImageUrlFrRecord(lpModel)}
          sx={{ '--Avatar-size': '64px' }}
        >
          {t('empty')}
        </Avatar>
        <div>
          <Stack
            direction="row"
            spacing={2}
            sx={{ alignItems: 'center', flexWrap: 'wrap' }}
          >
            <Typography variant="h4">{lpModel.cat_name}</Typography>
            <Chip
              icon={
                <CheckCircleIcon
                  color="var(--mui-palette-success-main)"
                  weight="fill"
                />
              }
              label={lpModel.visible}
              size="small"
              variant="outlined"
            />
          </Stack>
          <Typography
            color="text.secondary"
            variant="body1"
          >
            {lpModel.slug}
          </Typography>
        </div>
      </Stack>
      <div>
        <Button
          endIcon={<CaretDownIcon />}
          variant="contained"
        >
          {t('list.action')}
        </Button>
      </div>
    </>
  );
}

File: categories/create/page.tsx

'use client';

// RULES:
// T.B.A.
//
import * as React from 'react';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import { MfCategoryCreateForm } from '@/components/dashboard/mf/categories/mf-category-create-form';

export default function Page(): React.JSX.Element {
  // RULES: follow the name of page directory
  const { t } = useTranslation(['lp_categories']);

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack spacing={3}>
          <div>
            <Link
              color="text.primary"
              component={RouterLink}
              href={paths.dashboard.mf_categories.list}
              sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
              variant="subtitle2"
            >
              <ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
              {t('title')}
            </Link>
          </div>
          <div>
            <Typography variant="h4">{t('create.title')}</Typography>
          </div>
        </Stack>
        <MfCategoryCreateForm />
      </Stack>
    </Box>
  );
}

File: categories/edit/[cat_id]/_PROMPT.md

# task

## instruction

with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/_helloworld/page.tsx`

with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/lesson_types/edit/[typeId]/page.tsx`

please modify `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/lesson_categories/edit/page.tsx`

please draft a tsx for showing error to user thanks,

File: categories/edit/[cat_id]/page.tsx

'use client';

import * as React from 'react';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import { MfCategoryEditForm } from '@/components/dashboard/mf/categories/mf-category-edit-form';

export default function Page(): React.JSX.Element {
  const { t } = useTranslation(['lp_categories']);

  React.useEffect(() => {
    // console.log('helloworld');
  }, []);

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack spacing={3}>
          <div>
            <Link
              color="text.primary"
              component={RouterLink}
              href={paths.dashboard.mf_categories.list}
              sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
              variant="subtitle2"
            >
              <ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
              {t('edit.title')}
            </Link>
          </div>
          <div>
            <Typography variant="h4">{t('edit.title')}</Typography>
          </div>
        </Stack>
        <MfCategoryEditForm />
      </Stack>
    </Box>
  );
}

File: categories/lp-categories-sample-data.tsx

import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type';

export const LpCategoriesSampleData = [
  {
    id: 'USR-005',
    name: 'Fran Perez',
    avatar: '/assets/avatar-5.png',
    email: 'fran.perez@domain.com',
    phone: '(815) 704-0045',
    quota: 50,
    status: 'active',
    createdAt: dayjs().subtract(1, 'hour').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-004',
    name: 'Penjani Inyene',
    avatar: '/assets/avatar-4.png',
    email: 'penjani.inyene@domain.com',
    phone: '(803) 937-8925',
    quota: 100,
    status: 'active',
    createdAt: dayjs().subtract(3, 'hour').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-003',
    name: 'Carson Darrin',
    avatar: '/assets/avatar-3.png',
    email: 'carson.darrin@domain.com',
    phone: '(715) 278-5041',
    quota: 10,
    status: 'blocked',
    createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-002',
    name: 'Siegbert Gottfried',
    avatar: '/assets/avatar-2.png',
    email: 'siegbert.gottfried@domain.com',
    phone: '(603) 766-0431',
    quota: 0,
    status: 'pending',
    createdAt: dayjs().subtract(7, 'hour').subtract(1, 'day').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-001',
    name: 'Miron Vitold',
    avatar: '/assets/avatar-1.png',
    email: 'miron.vitold@domain.com',
    phone: '(425) 434-5535',
    quota: 50,
    status: 'active',
    createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
] satisfies LessonCategory[];

File: categories/page.tsx

'use client';

// RULES:
// contains list page for lp_categories (QuizLPCategories)
// contain definition to collection only
//
import * as React from 'react';
import { useRouter } from 'next/navigation';
import { COL_QUIZ_MF_CATEGORIES } from '@/constants';
import { LoadingButton } from '@mui/lab';
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import Divider from '@mui/material/Divider';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import type { ListResult, RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import isDevelopment from '@/lib/check-is-development';
import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error';
import { defaultMfCategory } from '@/components/dashboard/mf/categories/_constants';
import { MfCategoriesFilters } from '@/components/dashboard/mf/categories/mf-categories-filters';
import type { Filters } from '@/components/dashboard/mf/categories/mf-categories-filters';
import { MfCategoriesPagination } from '@/components/dashboard/mf/categories/mf-categories-pagination';
import { MfCategoriesSelectionProvider } from '@/components/dashboard/mf/categories/mf-categories-selection-context';
import { MfCategoriesTable } from '@/components/dashboard/mf/categories/mf-categories-table';
import type { MfCategory } from '@/components/dashboard/mf/categories/type';
import FormLoading from '@/components/loading';

export default function Page({ searchParams }: PageProps): React.JSX.Element {
  const { t } = useTranslation(['mf_categories']);
  const { email, phone, sortDir, status, name, visible, type } = searchParams;
  const router = useRouter();
  const [lessonCategoriesData, setLessonCategoriesData] = React.useState<MfCategory[]>([]);
  //

  const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
  const [showLoading, setShowLoading] = React.useState<boolean>(true);
  const [showError, setShowError] = React.useState({ show: false, detail: '' });
  //
  const [rowsPerPage, setRowsPerPage] = React.useState<number>(5);
  const [f, setF] = React.useState<MfCategory[]>([]);
  const [currentPage, setCurrentPage] = React.useState<number>(1);
  const [recordCount, setRecordCount] = React.useState<number>(0);
  const [listOption, setListOption] = React.useState({});
  const [listSort, setListSort] = React.useState({});

  //
  const sortedLessonCategories = applySort(lessonCategoriesData, sortDir);
  const filteredLessonCategories = applyFilters(sortedLessonCategories, { email, phone, status });

  const reloadRows = async (): Promise<void> => {
    try {
      const models: ListResult<RecordModel> = await pb
        .collection(COL_QUIZ_MF_CATEGORIES)
        .getList(currentPage + 1, rowsPerPage, listOption);
      const { items, totalItems } = models;
      const tempLessonTypes: MfCategory[] = items.map((lt) => {
        return { ...defaultMfCategory, ...lt };
      });

      setLessonCategoriesData(tempLessonTypes);
      setRecordCount(totalItems);
      setF(tempLessonTypes);
      // console.log({ currentPage, f });
    } catch (error) {
      //
      logger.error(error);
      setShowError({
        //
        show: true,
        detail: JSON.stringify(error, null, 2),
      });
    } finally {
      setShowLoading(false);
    }
  };

  const [lastListOption, setLastListOption] = React.useState({});
  const isFirstRun = React.useRef(false);
  React.useEffect(() => {
    if (!isFirstRun.current) {
      isFirstRun.current = true;
    } else {
      if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
        // reset page number as tab changes
        setLastListOption(listOption);
        setCurrentPage(0);
        void reloadRows();
      } else {
        void reloadRows();
      }
    }
  }, [currentPage, rowsPerPage, listOption]);

  React.useEffect(() => {
    let tempFilter = [],
      tempSortDir = '';

    if (visible) {
      tempFilter.push(`visible = "${visible}"`);
    }

    if (sortDir) {
      tempSortDir = `-created`;
    }

    if (name) {
      tempFilter.push(`name ~ "%${name}%"`);
    }

    if (type) {
      tempFilter.push(`type ~ "%${type}%"`);
    }

    let preFinalListOption = {};
    if (tempFilter.length > 0) {
      preFinalListOption = { filter: tempFilter.join(' && ') };
    }
    if (tempSortDir.length > 0) {
      preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
    }
    setListOption(preFinalListOption);
    // setListOption({
    //   filter: tempFilter.join(' && '),
    //   sort: tempSortDir,
    //   //
    // });
  }, [visible, sortDir, name, type]);

  // return <>helloworld</>;

  if (showLoading) return <FormLoading />;

  if (showError.show)
    return (
      <ErrorDisplay
        message={t('error.unable-to-process-request')}
        code={-1}
        details={showError.detail}
      />
    );

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack
          direction={{ xs: 'column', sm: 'row' }}
          spacing={3}
          sx={{ alignItems: 'flex-start' }}
        >
          <Box sx={{ flex: '1 1 auto' }}>
            <Typography variant="h4">{t('list.title')}</Typography>
          </Box>
          <Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
            <LoadingButton
              loading={isLoadingAddPage}
              onClick={(): void => {
                setIsLoadingAddPage(true);
                router.push(paths.dashboard.mf_categories.create);
              }}
              startIcon={<PlusIcon />}
              variant="contained"
            >
              {t('list.add')}
            </LoadingButton>
          </Box>
        </Stack>
        <MfCategoriesSelectionProvider lessonCategories={f}>
          <Card>
            <MfCategoriesFilters
              filters={{ email, phone, status, name, visible, type }}
              fullData={lessonCategoriesData}
              sortDir={sortDir}
            />
            <Divider />
            <Box sx={{ overflowX: 'auto' }}>
              <MfCategoriesTable
                reloadRows={reloadRows}
                rows={f}
              />
            </Box>
            <Divider />
            <MfCategoriesPagination
              count={recordCount}
              page={currentPage}
              rowsPerPage={rowsPerPage}
              setPage={setCurrentPage}
              setRowsPerPage={setRowsPerPage}
            />
          </Card>
        </MfCategoriesSelectionProvider>
      </Stack>
      <Box sx={{ display: isDevelopment ? 'block' : 'none' }}>
        <pre>{JSON.stringify(f, null, 2)}</pre>
      </Box>
    </Box>
  );
}

// Sorting and filtering has to be done on the server.

function applySort(row: MfCategory[], sortDir: 'asc' | 'desc' | undefined): MfCategory[] {
  return row.sort((a, b) => {
    if (sortDir === 'asc') {
      return a.createdAt.getTime() - b.createdAt.getTime();
    }

    return b.createdAt.getTime() - a.createdAt.getTime();
  });
}

function applyFilters(row: MfCategory[], { email, phone, status, name, visible }: Filters): MfCategory[] {
  return row.filter((item) => {
    if (email) {
      if (!item.email?.toLowerCase().includes(email.toLowerCase())) {
        return false;
      }
    }

    if (phone) {
      if (!item.phone?.toLowerCase().includes(phone.toLowerCase())) {
        return false;
      }
    }

    if (status) {
      if (item.status !== status) {
        return false;
      }
    }

    if (name) {
      if (!item.name?.toLowerCase().includes(name.toLowerCase())) {
        return false;
      }
    }

    if (visible) {
      if (!item.visible?.toLowerCase().includes(visible.toLowerCase())) {
        return false;
      }
    }

    return true;
  });
}

interface PageProps {
  searchParams: {
    email?: string;
    phone?: string;
    sortDir?: 'asc' | 'desc';
    status?: string;
    name?: string;
    visible?: string;
    type?: string;
    //
  };
}

File: questions/[cat_id]/BasicDetailCard.tsx

'use client';

import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import Card from '@mui/material/Card';
import CardHeader from '@mui/material/CardHeader';
import Chip from '@mui/material/Chip';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
import { useTranslation } from 'react-i18next';

import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list';
import { MfCategory } from '@/components/dashboard/mf/categories/type';

export default function BasicDetailCard({
  lpModel: model,
  handleEditClick,
}: {
  lpModel: MfCategory;
  handleEditClick: () => void;
}): React.JSX.Element {
  const { t } = useTranslation();

  return (
    <Card>
      <CardHeader
        action={
          <IconButton
            onClick={() => {
              handleEditClick();
            }}
          >
            <PencilSimpleIcon />
          </IconButton>
        }
        avatar={
          <Avatar>
            <UserIcon fontSize="var(--Icon-fontSize)" />
          </Avatar>
        }
        title={t('list.basic-details')}
      />
      <PropertyList
        divider={<Divider />}
        orientation="vertical"
        sx={{ '--PropertyItem-padding': '12px 24px' }}
      >
        {(
          [
            {
              key: 'Customer ID',
              value: (
                <Chip
                  label={model.id}
                  size="small"
                  variant="soft"
                />
              ),
            },
            { key: 'Name', value: model.cat_name },
            { key: 'Remarks', value: model.remarks },
            { key: 'Description', value: model.description },
          ] satisfies { key: string; value: React.ReactNode }[]
        ).map(
          (item): React.JSX.Element => (
            <PropertyItem
              key={item.key}
              name={item.key}
              value={item.value}
            />
          )
        )}
      </PropertyList>
    </Card>
  );
}

File: questions/[cat_id]/page.tsx

'use client';

import * as React from 'react';
import RouterLink from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import SampleAddressCard from '@/app/dashboard/Sample/AddressCard';
import { SampleNotifications } from '@/app/dashboard/Sample/Notifications';
import SamplePaymentCard from '@/app/dashboard/Sample/PaymentCard';
import SampleSecurityCard from '@/app/dashboard/Sample/SecurityCard';
import { COL_QUIZ_MF_QUESTIONS } from '@/constants';
import { Grid } from '@mui/material';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import type { RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error';
import { defaultMfQuestion } from '@/components/dashboard/mf/questions/_constants.ts';
import { Notifications } from '@/components/dashboard/mf/questions/notifications';
import type { MfQuestion } from '@/components/dashboard/mf/questions/type';
import FormLoading from '@/components/loading';

import BasicDetailCard from './BasicDetailCard';
import TitleCard from './TitleCard';

export default function Page(): React.JSX.Element {
  const { t } = useTranslation();
  const router = useRouter();
  //
  const { cat_id: catId } = useParams<{ cat_id: string }>();
  //
  const [showLoading, setShowLoading] = React.useState<boolean>(true);
  const [showError, setShowError] = React.useState({ show: false, detail: '' });

  //
  const [showLessonQuestion, setShowLessonQuestion] = React.useState<MfQuestion>(defaultMfQuestion);

  function handleEditClick() {
    router.push(paths.dashboard.mf_questions.edit(showLessonQuestion.id));
  }

  React.useEffect(() => {
    if (catId) {
      pb.collection(COL_QUIZ_MF_QUESTIONS)
        .getOne(catId)
        .then((model: RecordModel) => {
          setShowLessonQuestion({ ...defaultMfQuestion, ...model });
        })
        .catch((err) => {
          logger.error(err);
          toast(t('list.error'));

          setShowError({ show: true, detail: JSON.stringify(err) });
        })
        .finally(() => {
          setShowLoading(false);
        });
    }
  }, [catId]);

  if (showLoading) return <FormLoading />;
  if (showError.show)
    return (
      <ErrorDisplay
        message={t('error.unable-to-process-request')}
        code="500"
        details={showError.detail}
      />
    );

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack spacing={3}>
          <div>
            <Link
              color="text.primary"
              component={RouterLink}
              href={paths.dashboard.mf_questions.list}
              sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
              variant="subtitle2"
            >
              <ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
              {t('edit.title')}
            </Link>
          </div>
          <Stack
            direction={{ xs: 'column', sm: 'row' }}
            spacing={3}
            sx={{ alignItems: 'flex-start' }}
          >
            <TitleCard lpModel={showLessonQuestion} />
          </Stack>
        </Stack>
        <Grid
          container
          spacing={4}
        >
          <Grid
            lg={4}
            xs={12}
          >
            <Stack spacing={4}>
              <BasicDetailCard
                lpModel={showLessonQuestion}
                handleEditClick={handleEditClick}
              />
              <SampleSecurityCard />
            </Stack>
          </Grid>
          <Grid
            lg={8}
            xs={12}
          >
            <Stack spacing={4}>
              <SamplePaymentCard />
              <SampleAddressCard />
              <Notifications notifications={SampleNotifications} />
            </Stack>
          </Grid>
        </Grid>
      </Stack>
    </Box>
  );
}

File: questions/[cat_id]/TitleCard.tsx

'use client';

import * as React from 'react';
import { Button } from '@mui/material';
import Avatar from '@mui/material/Avatar';
import Chip from '@mui/material/Chip';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next';

import type { MfCategory } from '@/components/dashboard/mf/categories/type';

function getImageUrlFrRecord(record: MfCategory): string {
  return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}

export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): React.JSX.Element {
  const { t } = useTranslation();

  return (
    <>
      <Stack
        direction="row"
        spacing={2}
        sx={{ alignItems: 'center', flex: '1 1 auto' }}
      >
        <Avatar
          variant="rounded"
          src={getImageUrlFrRecord(lpModel)}
          sx={{ '--Avatar-size': '64px' }}
        >
          {t('empty')}
        </Avatar>
        <div>
          <Stack
            direction="row"
            spacing={2}
            sx={{ alignItems: 'center', flexWrap: 'wrap' }}
          >
            <Typography variant="h4">{lpModel.cat_name}</Typography>
            <Chip
              icon={
                <CheckCircleIcon
                  color="var(--mui-palette-success-main)"
                  weight="fill"
                />
              }
              label={lpModel.visible}
              size="small"
              variant="outlined"
            />
          </Stack>
          <Typography
            color="text.secondary"
            variant="body1"
          >
            {lpModel.slug}
          </Typography>
        </div>
      </Stack>
      <div>
        <Button
          endIcon={<CaretDownIcon />}
          variant="contained"
        >
          {t('list.action')}
        </Button>
      </div>
    </>
  );
}

File: questions/create/page.tsx

'use client';

// RULES:
// T.B.A.
//
import * as React from 'react';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import { MfQuestionCreateForm } from '@/components/dashboard/mf/questions/mf-question-create-form';

export default function Page(): React.JSX.Element {
  // RULES: follow the name of page directory
  const { t } = useTranslation(['lp_questions']);

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack spacing={3}>
          <div>
            <Link
              color="text.primary"
              component={RouterLink}
              href={paths.dashboard.mf_questions.list}
              sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
              variant="subtitle2"
            >
              <ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
              {t('title')}
            </Link>
          </div>
          <div>
            <Typography variant="h4">{t('create.title')}</Typography>
          </div>
        </Stack>
        <MfQuestionCreateForm />
      </Stack>
    </Box>
  );
}

File: questions/edit/[cat_id]/_PROMPT.md

# task

## instruction

with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/_helloworld/page.tsx`

with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/lesson_types/edit/[typeId]/page.tsx`

please modify `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/lesson_categories/edit/page.tsx`

please draft a tsx for showing error to user thanks,

File: questions/edit/[cat_id]/page.tsx

'use client';

import * as React from 'react';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import { MfQuestionEditForm } from '@/components/dashboard/mf/questions/mf-question-edit-form';

export default function Page(): React.JSX.Element {
  const { t } = useTranslation(['lp_questions']);

  React.useEffect(() => {
    // console.log('helloworld');
  }, []);

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack spacing={3}>
          <div>
            <Link
              color="text.primary"
              component={RouterLink}
              href={paths.dashboard.mf_questions.list}
              sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
              variant="subtitle2"
            >
              <ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
              {t('edit.title')}
            </Link>
          </div>
          <div>
            <Typography variant="h4">{t('edit.title')}</Typography>
          </div>
        </Stack>
        <MfQuestionEditForm />
      </Stack>
    </Box>
  );
}

File: questions/lp-categories-sample-data.tsx

import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type';

export const LpCategoriesSampleData = [
  {
    id: 'USR-005',
    name: 'Fran Perez',
    avatar: '/assets/avatar-5.png',
    email: 'fran.perez@domain.com',
    phone: '(815) 704-0045',
    quota: 50,
    status: 'active',
    createdAt: dayjs().subtract(1, 'hour').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-004',
    name: 'Penjani Inyene',
    avatar: '/assets/avatar-4.png',
    email: 'penjani.inyene@domain.com',
    phone: '(803) 937-8925',
    quota: 100,
    status: 'active',
    createdAt: dayjs().subtract(3, 'hour').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-003',
    name: 'Carson Darrin',
    avatar: '/assets/avatar-3.png',
    email: 'carson.darrin@domain.com',
    phone: '(715) 278-5041',
    quota: 10,
    status: 'blocked',
    createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-002',
    name: 'Siegbert Gottfried',
    avatar: '/assets/avatar-2.png',
    email: 'siegbert.gottfried@domain.com',
    phone: '(603) 766-0431',
    quota: 0,
    status: 'pending',
    createdAt: dayjs().subtract(7, 'hour').subtract(1, 'day').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-001',
    name: 'Miron Vitold',
    avatar: '/assets/avatar-1.png',
    email: 'miron.vitold@domain.com',
    phone: '(425) 434-5535',
    quota: 50,
    status: 'active',
    createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
] satisfies LessonCategory[];

File: questions/page.tsx

'use client';

// RULES:
// contains list page for lp_questions (QuizLPQuestions)
// contain definition to collection only
//
import * as React from 'react';
import { useRouter } from 'next/navigation';
import { COL_QUIZ_MF_QUESTIONS } from '@/constants';
import { LoadingButton } from '@mui/lab';
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import Divider from '@mui/material/Divider';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import type { ListResult, RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import isDevelopment from '@/lib/check-is-development';
import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error';
import { defaultMfQuestion } from '@/components/dashboard/mf/questions/_constants';
import { MfQuestionsFilters } from '@/components/dashboard/mf/questions/mf-questions-filters';
import type { Filters } from '@/components/dashboard/mf/questions/mf-questions-filters';
import { MfQuestionsPagination } from '@/components/dashboard/mf/questions/mf-questions-pagination';
import { MfQuestionsSelectionProvider } from '@/components/dashboard/mf/questions/mf-questions-selection-context';
import { MfQuestionsTable } from '@/components/dashboard/mf/questions/mf-questions-table';
import type { MfQuestion } from '@/components/dashboard/mf/questions/type';
import FormLoading from '@/components/loading';

export default function Page({ searchParams }: PageProps): React.JSX.Element {
  const { t } = useTranslation(['mf_question']);
  const { email, phone, sortDir, status, name, visible, type } = searchParams;
  const router = useRouter();
  const [lessonQuestionsData, setLessonCategoriesData] = React.useState<MfQuestion[]>([]);
  //

  const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
  const [showLoading, setShowLoading] = React.useState<boolean>(true);
  const [showError, setShowError] = React.useState({ show: false, detail: '' });
  //
  const [rowsPerPage, setRowsPerPage] = React.useState<number>(5);
  const [f, setF] = React.useState<MfQuestion[]>([]);
  const [currentPage, setCurrentPage] = React.useState<number>(0);
  const [recordCount, setRecordCount] = React.useState<number>(0);
  const [listOption, setListOption] = React.useState({});
  const [listSort, setListSort] = React.useState({});

  //
  const sortedLessonCategories = applySort(lessonQuestionsData, sortDir);
  const filteredLessonCategories = applyFilters(sortedLessonCategories, { email, phone, status });

  const reloadRows = async (): Promise<void> => {
    try {
      const models: ListResult<RecordModel> = await pb
        .collection(COL_QUIZ_MF_QUESTIONS)
        .getList(currentPage + 1, rowsPerPage, listOption);
      const { items, totalItems } = models;
      const tempLessonTypes: MfQuestion[] = items.map((lt) => {
        return { ...defaultMfQuestion, ...lt };
      });

      setLessonCategoriesData(tempLessonTypes);
      setRecordCount(totalItems);
      setF(tempLessonTypes);
      // console.log({ currentPage, f });
    } catch (error) {
      //
      logger.error(error);
      setShowError({
        //
        show: true,
        detail: JSON.stringify(error, null, 2),
      });
    } finally {
      setShowLoading(false);
    }
  };

  const [lastListOption, setLastListOption] = React.useState({});
  const isFirstRun = React.useRef(false);
  React.useEffect(() => {
    if (!isFirstRun.current) {
      isFirstRun.current = true;
    } else {
      if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
        // reset page number as tab changes
        setLastListOption(listOption);
        setCurrentPage(0);
        void reloadRows();
      } else {
        void reloadRows();
      }
    }
  }, [currentPage, rowsPerPage, listOption]);

  React.useEffect(() => {
    let tempFilter = [],
      tempSortDir = '';

    if (visible) {
      tempFilter.push(`visible = "${visible}"`);
    }

    if (sortDir) {
      tempSortDir = `-created`;
    }

    if (name) {
      tempFilter.push(`name ~ "%${name}%"`);
    }

    if (type) {
      tempFilter.push(`type ~ "%${type}%"`);
    }

    let preFinalListOption = {};
    if (tempFilter.length > 0) {
      preFinalListOption = { filter: tempFilter.join(' && ') };
    }
    if (tempSortDir.length > 0) {
      preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
    }
    setListOption(preFinalListOption);
    // setListOption({
    //   filter: tempFilter.join(' && '),
    //   sort: tempSortDir,
    //   //
    // });
  }, [visible, sortDir, name, type]);

  // return <>helloworld</>;

  if (showLoading) return <FormLoading />;

  if (showError.show)
    return (
      <ErrorDisplay
        message={t('error.unable-to-process-request')}
        code={-1}
        details={showError.detail}
      />
    );

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack
          direction={{ xs: 'column', sm: 'row' }}
          spacing={3}
          sx={{ alignItems: 'flex-start' }}
        >
          <Box sx={{ flex: '1 1 auto' }}>
            <Typography variant="h4">{t('list.title')}</Typography>
          </Box>
          <Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
            <LoadingButton
              loading={isLoadingAddPage}
              onClick={(): void => {
                setIsLoadingAddPage(true);
                router.push(paths.dashboard.lp_questions.create);
              }}
              startIcon={<PlusIcon />}
              variant="contained"
            >
              {t('list.add')}
            </LoadingButton>
          </Box>
        </Stack>
        <MfQuestionsSelectionProvider lessonQuestions={f}>
          <Card>
            <MfQuestionsFilters
              filters={{ email, phone, status, name, visible, type }}
              fullData={lessonQuestionsData}
              sortDir={sortDir}
            />
            <Divider />
            <Box sx={{ overflowX: 'auto' }}>
              <MfQuestionsTable
                reloadRows={reloadRows}
                rows={f}
              />
            </Box>
            <Divider />
            <MfQuestionsPagination
              count={recordCount}
              page={currentPage}
              rowsPerPage={rowsPerPage}
              setPage={setCurrentPage}
              setRowsPerPage={setRowsPerPage}
            />
          </Card>
        </MfQuestionsSelectionProvider>
      </Stack>
      <Box sx={{ display: isDevelopment ? 'block' : 'none' }}>
        <pre>{JSON.stringify(f, null, 2)}</pre>
      </Box>
    </Box>
  );
}

// Sorting and filtering has to be done on the server.

function applySort(row: MfQuestion[], sortDir: 'asc' | 'desc' | undefined): MfQuestion[] {
  return row.sort((a, b) => {
    if (sortDir === 'asc') {
      return a.createdAt.getTime() - b.createdAt.getTime();
    }

    return b.createdAt.getTime() - a.createdAt.getTime();
  });
}

function applyFilters(row: MfQuestion[], { email, phone, status, name, visible }: Filters): MfQuestion[] {
  return row.filter((item) => {
    if (email) {
      if (!item.email?.toLowerCase().includes(email.toLowerCase())) {
        return false;
      }
    }

    if (phone) {
      if (!item.phone?.toLowerCase().includes(phone.toLowerCase())) {
        return false;
      }
    }

    if (status) {
      if (item.status !== status) {
        return false;
      }
    }

    if (name) {
      if (!item.name?.toLowerCase().includes(name.toLowerCase())) {
        return false;
      }
    }

    if (visible) {
      if (!item.visible?.toLowerCase().includes(visible.toLowerCase())) {
        return false;
      }
    }

    return true;
  });
}

interface PageProps {
  searchParams: {
    email?: string;
    phone?: string;
    sortDir?: 'asc' | 'desc';
    status?: string;
    name?: string;
    visible?: string;
    type?: string;
    //
  };
}

File: repomix-output.xml

This file is a merged representation of the entire codebase, combined into a single document by Repomix.

<file_summary>
This section contains a summary of this file.

<purpose>
This file contains a packed representation of the entire repository's contents.
It is designed to be easily consumable by AI systems for analysis, code review,
or other automated processes.
</purpose>

<file_format>
The content is organized as follows:
1. This summary section
2. Repository information
3. Directory structure
4. Repository files, each consisting of:
  - File path as an attribute
  - Full contents of the file
</file_format>

<usage_guidelines>
- This file should be treated as read-only. Any changes should be made to the
  original repository files, not this packed version.
- When processing this file, use the file path to distinguish
  between different files in the repository.
- Be aware that this file may contain sensitive information. Handle it with
  the same level of security as you would the original repository.
</usage_guidelines>

<notes>
- Some files may have been excluded based on .gitignore rules and Repomix's configuration
- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files
- Files matching patterns in .gitignore are excluded
- Files matching default ignore patterns are excluded
- Files are sorted by Git change count (files with more changes are at the bottom)
</notes>

<additional_info>

</additional_info>

</file_summary>

<directory_structure>
categories/
  [cat_id]/
    BasicDetailCard.tsx
    page.tsx
    TitleCard.tsx
  create/
    page.tsx
  edit/
    [cat_id]/
      _PROMPT.md
      page.tsx
  lp-categories-sample-data.tsx
  page.tsx
questions/
  [cat_id]/
    BasicDetailCard.tsx
    page.tsx
    TitleCard.tsx
  create/
    page.tsx
  edit/
    [cat_id]/
      _PROMPT.md
      page.tsx
  lp-categories-sample-data.tsx
  page.tsx
</directory_structure>

<files>
This section contains the contents of the repository's files.

<file path="categories/[cat_id]/BasicDetailCard.tsx">
'use client';

import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import Card from '@mui/material/Card';
import CardHeader from '@mui/material/CardHeader';
import Chip from '@mui/material/Chip';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
import { useTranslation } from 'react-i18next';

import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list';
import type { MfCategory } from '@/components/dashboard/mf/categories/type';

export default function BasicDetailCard({
  lpModel: model,
  handleEditClick,
}: {
  lpModel: MfCategory;
  handleEditClick: () => void;
}): React.JSX.Element {
  const { t } = useTranslation();

  return (
    <Card>
      <CardHeader
        action={
          <IconButton
            onClick={() => {
              handleEditClick();
            }}
          >
            <PencilSimpleIcon />
          </IconButton>
        }
        avatar={
          <Avatar>
            <UserIcon fontSize="var(--Icon-fontSize)" />
          </Avatar>
        }
        title={t('list.basic-details')}
      />
      <PropertyList
        divider={<Divider />}
        orientation="vertical"
        sx={{ '--PropertyItem-padding': '12px 24px' }}
      >
        {(
          [
            {
              key: 'Customer ID',
              value: (
                <Chip
                  label={model.id}
                  size="small"
                  variant="soft"
                />
              ),
            },
            { key: 'Name', value: model.cat_name },
            { key: 'Remarks', value: model.remarks },
            { key: 'Description', value: model.description },
          ] satisfies { key: string; value: React.ReactNode }[]
        ).map(
          (item): React.JSX.Element => (
            <PropertyItem
              key={item.key}
              name={item.key}
              value={item.value}
            />
          )
        )}
      </PropertyList>
    </Card>
  );
}
</file>

<file path="categories/[cat_id]/page.tsx">
'use client';

import * as React from 'react';
import RouterLink from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import SampleAddressCard from '@/app/dashboard/Sample/AddressCard';
import { SampleNotifications } from '@/app/dashboard/Sample/Notifications';
import SamplePaymentCard from '@/app/dashboard/Sample/PaymentCard';
import SampleSecurityCard from '@/app/dashboard/Sample/SecurityCard';
import { COL_QUIZ_MF_CATEGORIES } from '@/constants';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Grid from '@mui/material/Unstable_Grid2';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import type { RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error';
import { defaultMfCategory } from '@/components/dashboard/mf/categories/_constants.ts';
import { Notifications } from '@/components/dashboard/mf/categories/notifications';
import type { MfCategory } from '@/components/dashboard/mf/categories/type';
import FormLoading from '@/components/loading';

import BasicDetailCard from './BasicDetailCard';
import TitleCard from './TitleCard';

export default function Page(): React.JSX.Element {
  const { t } = useTranslation();
  const router = useRouter();
  //
  const { cat_id: catId } = useParams<{ cat_id: string }>();
  //
  const [showLoading, setShowLoading] = React.useState<boolean>(true);
  const [showError, setShowError] = React.useState({ show: false, detail: '' });

  //
  const [showLessonCategory, setShowLessonCategory] = React.useState<MfCategory>(defaultMfCategory);

  function handleEditClick() {
    router.push(paths.dashboard.mf_categories.edit(showLessonCategory.id));
  }

  React.useEffect(() => {
    if (catId) {
      pb.collection(COL_QUIZ_MF_CATEGORIES)
        .getOne(catId)
        .then((model: RecordModel) => {
          setShowLessonCategory({ ...defaultMfCategory, ...model });
        })
        .catch((err) => {
          logger.error(err);
          toast(t('list.error'));

          setShowError({ show: true, detail: JSON.stringify(err) });
        })
        .finally(() => {
          setShowLoading(false);
        });
    }
  }, [catId]);

  if (showLoading) return <FormLoading />;
  if (showError.show)
    return (
      <ErrorDisplay
        message={t('error.unable-to-process-request')}
        code="500"
        details={showError.detail}
      />
    );

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack spacing={3}>
          <div>
            <Link
              color="text.primary"
              component={RouterLink}
              href={paths.dashboard.mf_categories.list}
              sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
              variant="subtitle2"
            >
              <ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
              {t('list.title')}
            </Link>
          </div>
          <Stack
            direction={{ xs: 'column', sm: 'row' }}
            spacing={3}
            sx={{ alignItems: 'flex-start' }}
          >
            <TitleCard lpModel={showLessonCategory} />
          </Stack>
        </Stack>
        <Grid
          container
          spacing={4}
        >
          <Grid
            lg={4}
            xs={12}
          >
            <Stack spacing={4}>
              <BasicDetailCard
                lpModel={showLessonCategory}
                handleEditClick={handleEditClick}
              />
              <SampleSecurityCard />
            </Stack>
          </Grid>
          <Grid
            lg={8}
            xs={12}
          >
            <Stack spacing={4}>
              <SamplePaymentCard />
              <SampleAddressCard />
              <Notifications notifications={SampleNotifications} />
            </Stack>
          </Grid>
        </Grid>
      </Stack>
    </Box>
  );
}
</file>

<file path="categories/[cat_id]/TitleCard.tsx">
'use client';

import * as React from 'react';
import { Button } from '@mui/material';
import Avatar from '@mui/material/Avatar';
import Chip from '@mui/material/Chip';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next';

import type { MfCategory } from '@/components/dashboard/mf/categories/type';

function getImageUrlFrRecord(record: MfCategory): string {
  return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}

export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): React.JSX.Element {
  const { t } = useTranslation();

  return (
    <>
      <Stack
        direction="row"
        spacing={2}
        sx={{ alignItems: 'center', flex: '1 1 auto' }}
      >
        <Avatar
          variant="rounded"
          src={getImageUrlFrRecord(lpModel)}
          sx={{ '--Avatar-size': '64px' }}
        >
          {t('empty')}
        </Avatar>
        <div>
          <Stack
            direction="row"
            spacing={2}
            sx={{ alignItems: 'center', flexWrap: 'wrap' }}
          >
            <Typography variant="h4">{lpModel.cat_name}</Typography>
            <Chip
              icon={
                <CheckCircleIcon
                  color="var(--mui-palette-success-main)"
                  weight="fill"
                />
              }
              label={lpModel.visible}
              size="small"
              variant="outlined"
            />
          </Stack>
          <Typography
            color="text.secondary"
            variant="body1"
          >
            {lpModel.slug}
          </Typography>
        </div>
      </Stack>
      <div>
        <Button
          endIcon={<CaretDownIcon />}
          variant="contained"
        >
          {t('list.action')}
        </Button>
      </div>
    </>
  );
}
</file>

<file path="categories/create/page.tsx">
'use client';

// RULES:
// T.B.A.
//
import * as React from 'react';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import { MfCategoryCreateForm } from '@/components/dashboard/mf/categories/mf-category-create-form';

export default function Page(): React.JSX.Element {
  // RULES: follow the name of page directory
  const { t } = useTranslation(['lp_categories']);

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack spacing={3}>
          <div>
            <Link
              color="text.primary"
              component={RouterLink}
              href={paths.dashboard.mf_categories.list}
              sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
              variant="subtitle2"
            >
              <ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
              {t('title')}
            </Link>
          </div>
          <div>
            <Typography variant="h4">{t('create.title')}</Typography>
          </div>
        </Stack>
        <MfCategoryCreateForm />
      </Stack>
    </Box>
  );
}
</file>

<file path="categories/edit/[cat_id]/_PROMPT.md">
# task

## instruction

with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/_helloworld/page.tsx`

with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/lesson_types/edit/[typeId]/page.tsx`

please modify `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/lesson_categories/edit/page.tsx`

please draft a tsx for showing error to user thanks,
</file>

<file path="categories/edit/[cat_id]/page.tsx">
'use client';

import * as React from 'react';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import { MfCategoryEditForm } from '@/components/dashboard/mf/categories/mf-category-edit-form';

export default function Page(): React.JSX.Element {
  const { t } = useTranslation(['lp_categories']);

  React.useEffect(() => {
    // console.log('helloworld');
  }, []);

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack spacing={3}>
          <div>
            <Link
              color="text.primary"
              component={RouterLink}
              href={paths.dashboard.mf_categories.list}
              sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
              variant="subtitle2"
            >
              <ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
              {t('edit.title')}
            </Link>
          </div>
          <div>
            <Typography variant="h4">{t('edit.title')}</Typography>
          </div>
        </Stack>
        <MfCategoryEditForm />
      </Stack>
    </Box>
  );
}
</file>

<file path="categories/lp-categories-sample-data.tsx">
import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type';

export const LpCategoriesSampleData = [
  {
    id: 'USR-005',
    name: 'Fran Perez',
    avatar: '/assets/avatar-5.png',
    email: 'fran.perez@domain.com',
    phone: '(815) 704-0045',
    quota: 50,
    status: 'active',
    createdAt: dayjs().subtract(1, 'hour').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-004',
    name: 'Penjani Inyene',
    avatar: '/assets/avatar-4.png',
    email: 'penjani.inyene@domain.com',
    phone: '(803) 937-8925',
    quota: 100,
    status: 'active',
    createdAt: dayjs().subtract(3, 'hour').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-003',
    name: 'Carson Darrin',
    avatar: '/assets/avatar-3.png',
    email: 'carson.darrin@domain.com',
    phone: '(715) 278-5041',
    quota: 10,
    status: 'blocked',
    createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-002',
    name: 'Siegbert Gottfried',
    avatar: '/assets/avatar-2.png',
    email: 'siegbert.gottfried@domain.com',
    phone: '(603) 766-0431',
    quota: 0,
    status: 'pending',
    createdAt: dayjs().subtract(7, 'hour').subtract(1, 'day').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-001',
    name: 'Miron Vitold',
    avatar: '/assets/avatar-1.png',
    email: 'miron.vitold@domain.com',
    phone: '(425) 434-5535',
    quota: 50,
    status: 'active',
    createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
] satisfies LessonCategory[];
</file>

<file path="categories/page.tsx">
'use client';

// RULES:
// contains list page for lp_categories (QuizLPCategories)
// contain definition to collection only
//
import * as React from 'react';
import { useRouter } from 'next/navigation';
import { COL_QUIZ_MF_CATEGORIES } from '@/constants';
import { LoadingButton } from '@mui/lab';
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import Divider from '@mui/material/Divider';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import type { ListResult, RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import isDevelopment from '@/lib/check-is-development';
import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error';
import { defaultMfCategory } from '@/components/dashboard/mf/categories/_constants';
import { MfCategoriesFilters } from '@/components/dashboard/mf/categories/mf-categories-filters';
import type { Filters } from '@/components/dashboard/mf/categories/mf-categories-filters';
import { MfCategoriesPagination } from '@/components/dashboard/mf/categories/mf-categories-pagination';
import { MfCategoriesSelectionProvider } from '@/components/dashboard/mf/categories/mf-categories-selection-context';
import { MfCategoriesTable } from '@/components/dashboard/mf/categories/mf-categories-table';
import type { MfCategory } from '@/components/dashboard/mf/categories/type';
import FormLoading from '@/components/loading';

export default function Page({ searchParams }: PageProps): React.JSX.Element {
  const { t } = useTranslation(['mf_categories']);
  const { email, phone, sortDir, status, name, visible, type } = searchParams;
  const router = useRouter();
  const [lessonCategoriesData, setLessonCategoriesData] = React.useState<MfCategory[]>([]);
  //

  const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
  const [showLoading, setShowLoading] = React.useState<boolean>(true);
  const [showError, setShowError] = React.useState({ show: false, detail: '' });
  //
  const [rowsPerPage, setRowsPerPage] = React.useState<number>(5);
  const [f, setF] = React.useState<MfCategory[]>([]);
  const [currentPage, setCurrentPage] = React.useState<number>(1);
  const [recordCount, setRecordCount] = React.useState<number>(0);
  const [listOption, setListOption] = React.useState({});
  const [listSort, setListSort] = React.useState({});

  //
  const sortedLessonCategories = applySort(lessonCategoriesData, sortDir);
  const filteredLessonCategories = applyFilters(sortedLessonCategories, { email, phone, status });

  const reloadRows = async (): Promise<void> => {
    try {
      const models: ListResult<RecordModel> = await pb
        .collection(COL_QUIZ_MF_CATEGORIES)
        .getList(currentPage + 1, rowsPerPage, listOption);
      const { items, totalItems } = models;
      const tempLessonTypes: MfCategory[] = items.map((lt) => {
        return { ...defaultMfCategory, ...lt };
      });

      setLessonCategoriesData(tempLessonTypes);
      setRecordCount(totalItems);
      setF(tempLessonTypes);
      // console.log({ currentPage, f });
    } catch (error) {
      //
      logger.error(error);
      setShowError({
        //
        show: true,
        detail: JSON.stringify(error, null, 2),
      });
    } finally {
      setShowLoading(false);
    }
  };

  const [lastListOption, setLastListOption] = React.useState({});
  const isFirstRun = React.useRef(false);
  React.useEffect(() => {
    if (!isFirstRun.current) {
      isFirstRun.current = true;
    } else {
      if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
        // reset page number as tab changes
        setLastListOption(listOption);
        setCurrentPage(0);
        void reloadRows();
      } else {
        void reloadRows();
      }
    }
  }, [currentPage, rowsPerPage, listOption]);

  React.useEffect(() => {
    let tempFilter = [],
      tempSortDir = '';

    if (visible) {
      tempFilter.push(`visible = "${visible}"`);
    }

    if (sortDir) {
      tempSortDir = `-created`;
    }

    if (name) {
      tempFilter.push(`name ~ "%${name}%"`);
    }

    if (type) {
      tempFilter.push(`type ~ "%${type}%"`);
    }

    let preFinalListOption = {};
    if (tempFilter.length > 0) {
      preFinalListOption = { filter: tempFilter.join(' && ') };
    }
    if (tempSortDir.length > 0) {
      preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
    }
    setListOption(preFinalListOption);
    // setListOption({
    //   filter: tempFilter.join(' && '),
    //   sort: tempSortDir,
    //   //
    // });
  }, [visible, sortDir, name, type]);

  // return <>helloworld</>;

  if (showLoading) return <FormLoading />;

  if (showError.show)
    return (
      <ErrorDisplay
        message={t('error.unable-to-process-request')}
        code={-1}
        details={showError.detail}
      />
    );

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack
          direction={{ xs: 'column', sm: 'row' }}
          spacing={3}
          sx={{ alignItems: 'flex-start' }}
        >
          <Box sx={{ flex: '1 1 auto' }}>
            <Typography variant="h4">{t('list.title')}</Typography>
          </Box>
          <Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
            <LoadingButton
              loading={isLoadingAddPage}
              onClick={(): void => {
                setIsLoadingAddPage(true);
                router.push(paths.dashboard.mf_categories.create);
              }}
              startIcon={<PlusIcon />}
              variant="contained"
            >
              {t('list.add')}
            </LoadingButton>
          </Box>
        </Stack>
        <MfCategoriesSelectionProvider lessonCategories={f}>
          <Card>
            <MfCategoriesFilters
              filters={{ email, phone, status, name, visible, type }}
              fullData={lessonCategoriesData}
              sortDir={sortDir}
            />
            <Divider />
            <Box sx={{ overflowX: 'auto' }}>
              <MfCategoriesTable
                reloadRows={reloadRows}
                rows={f}
              />
            </Box>
            <Divider />
            <MfCategoriesPagination
              count={recordCount}
              page={currentPage}
              rowsPerPage={rowsPerPage}
              setPage={setCurrentPage}
              setRowsPerPage={setRowsPerPage}
            />
          </Card>
        </MfCategoriesSelectionProvider>
      </Stack>
      <Box sx={{ display: isDevelopment ? 'block' : 'none' }}>
        <pre>{JSON.stringify(f, null, 2)}</pre>
      </Box>
    </Box>
  );
}

// Sorting and filtering has to be done on the server.

function applySort(row: MfCategory[], sortDir: 'asc' | 'desc' | undefined): MfCategory[] {
  return row.sort((a, b) => {
    if (sortDir === 'asc') {
      return a.createdAt.getTime() - b.createdAt.getTime();
    }

    return b.createdAt.getTime() - a.createdAt.getTime();
  });
}

function applyFilters(row: MfCategory[], { email, phone, status, name, visible }: Filters): MfCategory[] {
  return row.filter((item) => {
    if (email) {
      if (!item.email?.toLowerCase().includes(email.toLowerCase())) {
        return false;
      }
    }

    if (phone) {
      if (!item.phone?.toLowerCase().includes(phone.toLowerCase())) {
        return false;
      }
    }

    if (status) {
      if (item.status !== status) {
        return false;
      }
    }

    if (name) {
      if (!item.name?.toLowerCase().includes(name.toLowerCase())) {
        return false;
      }
    }

    if (visible) {
      if (!item.visible?.toLowerCase().includes(visible.toLowerCase())) {
        return false;
      }
    }

    return true;
  });
}

interface PageProps {
  searchParams: {
    email?: string;
    phone?: string;
    sortDir?: 'asc' | 'desc';
    status?: string;
    name?: string;
    visible?: string;
    type?: string;
    //
  };
}
</file>

<file path="questions/[cat_id]/BasicDetailCard.tsx">
'use client';

import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import Card from '@mui/material/Card';
import CardHeader from '@mui/material/CardHeader';
import Chip from '@mui/material/Chip';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
import { useTranslation } from 'react-i18next';

import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list';
import { MfCategory } from '@/components/dashboard/mf/categories/type';

export default function BasicDetailCard({
  lpModel: model,
  handleEditClick,
}: {
  lpModel: MfCategory;
  handleEditClick: () => void;
}): React.JSX.Element {
  const { t } = useTranslation();

  return (
    <Card>
      <CardHeader
        action={
          <IconButton
            onClick={() => {
              handleEditClick();
            }}
          >
            <PencilSimpleIcon />
          </IconButton>
        }
        avatar={
          <Avatar>
            <UserIcon fontSize="var(--Icon-fontSize)" />
          </Avatar>
        }
        title={t('list.basic-details')}
      />
      <PropertyList
        divider={<Divider />}
        orientation="vertical"
        sx={{ '--PropertyItem-padding': '12px 24px' }}
      >
        {(
          [
            {
              key: 'Customer ID',
              value: (
                <Chip
                  label={model.id}
                  size="small"
                  variant="soft"
                />
              ),
            },
            { key: 'Name', value: model.cat_name },
            { key: 'Remarks', value: model.remarks },
            { key: 'Description', value: model.description },
          ] satisfies { key: string; value: React.ReactNode }[]
        ).map(
          (item): React.JSX.Element => (
            <PropertyItem
              key={item.key}
              name={item.key}
              value={item.value}
            />
          )
        )}
      </PropertyList>
    </Card>
  );
}
</file>

<file path="questions/[cat_id]/page.tsx">
'use client';

import * as React from 'react';
import RouterLink from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import SampleAddressCard from '@/app/dashboard/Sample/AddressCard';
import { SampleNotifications } from '@/app/dashboard/Sample/Notifications';
import SamplePaymentCard from '@/app/dashboard/Sample/PaymentCard';
import SampleSecurityCard from '@/app/dashboard/Sample/SecurityCard';
import { COL_QUIZ_MF_QUESTIONS } from '@/constants';
import { Grid } from '@mui/material';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import type { RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error';
import { defaultMfQuestion } from '@/components/dashboard/mf/questions/_constants.ts';
import { Notifications } from '@/components/dashboard/mf/questions/notifications';
import type { MfQuestion } from '@/components/dashboard/mf/questions/type';
import FormLoading from '@/components/loading';

import BasicDetailCard from './BasicDetailCard';
import TitleCard from './TitleCard';

export default function Page(): React.JSX.Element {
  const { t } = useTranslation();
  const router = useRouter();
  //
  const { cat_id: catId } = useParams<{ cat_id: string }>();
  //
  const [showLoading, setShowLoading] = React.useState<boolean>(true);
  const [showError, setShowError] = React.useState({ show: false, detail: '' });

  //
  const [showLessonQuestion, setShowLessonQuestion] = React.useState<MfQuestion>(defaultMfQuestion);

  function handleEditClick() {
    router.push(paths.dashboard.mf_questions.edit(showLessonQuestion.id));
  }

  React.useEffect(() => {
    if (catId) {
      pb.collection(COL_QUIZ_MF_QUESTIONS)
        .getOne(catId)
        .then((model: RecordModel) => {
          setShowLessonQuestion({ ...defaultMfQuestion, ...model });
        })
        .catch((err) => {
          logger.error(err);
          toast(t('list.error'));

          setShowError({ show: true, detail: JSON.stringify(err) });
        })
        .finally(() => {
          setShowLoading(false);
        });
    }
  }, [catId]);

  if (showLoading) return <FormLoading />;
  if (showError.show)
    return (
      <ErrorDisplay
        message={t('error.unable-to-process-request')}
        code="500"
        details={showError.detail}
      />
    );

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack spacing={3}>
          <div>
            <Link
              color="text.primary"
              component={RouterLink}
              href={paths.dashboard.mf_questions.list}
              sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
              variant="subtitle2"
            >
              <ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
              {t('edit.title')}
            </Link>
          </div>
          <Stack
            direction={{ xs: 'column', sm: 'row' }}
            spacing={3}
            sx={{ alignItems: 'flex-start' }}
          >
            <TitleCard lpModel={showLessonQuestion} />
          </Stack>
        </Stack>
        <Grid
          container
          spacing={4}
        >
          <Grid
            lg={4}
            xs={12}
          >
            <Stack spacing={4}>
              <BasicDetailCard
                lpModel={showLessonQuestion}
                handleEditClick={handleEditClick}
              />
              <SampleSecurityCard />
            </Stack>
          </Grid>
          <Grid
            lg={8}
            xs={12}
          >
            <Stack spacing={4}>
              <SamplePaymentCard />
              <SampleAddressCard />
              <Notifications notifications={SampleNotifications} />
            </Stack>
          </Grid>
        </Grid>
      </Stack>
    </Box>
  );
}
</file>

<file path="questions/[cat_id]/TitleCard.tsx">
'use client';

import * as React from 'react';
import { Button } from '@mui/material';
import Avatar from '@mui/material/Avatar';
import Chip from '@mui/material/Chip';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next';

import type { MfCategory } from '@/components/dashboard/mf/categories/type';

function getImageUrlFrRecord(record: MfCategory): string {
  return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}

export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): React.JSX.Element {
  const { t } = useTranslation();

  return (
    <>
      <Stack
        direction="row"
        spacing={2}
        sx={{ alignItems: 'center', flex: '1 1 auto' }}
      >
        <Avatar
          variant="rounded"
          src={getImageUrlFrRecord(lpModel)}
          sx={{ '--Avatar-size': '64px' }}
        >
          {t('empty')}
        </Avatar>
        <div>
          <Stack
            direction="row"
            spacing={2}
            sx={{ alignItems: 'center', flexWrap: 'wrap' }}
          >
            <Typography variant="h4">{lpModel.cat_name}</Typography>
            <Chip
              icon={
                <CheckCircleIcon
                  color="var(--mui-palette-success-main)"
                  weight="fill"
                />
              }
              label={lpModel.visible}
              size="small"
              variant="outlined"
            />
          </Stack>
          <Typography
            color="text.secondary"
            variant="body1"
          >
            {lpModel.slug}
          </Typography>
        </div>
      </Stack>
      <div>
        <Button
          endIcon={<CaretDownIcon />}
          variant="contained"
        >
          {t('list.action')}
        </Button>
      </div>
    </>
  );
}
</file>

<file path="questions/create/page.tsx">
'use client';

// RULES:
// T.B.A.
//
import * as React from 'react';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import { MfQuestionCreateForm } from '@/components/dashboard/mf/questions/mf-question-create-form';

export default function Page(): React.JSX.Element {
  // RULES: follow the name of page directory
  const { t } = useTranslation(['lp_questions']);

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack spacing={3}>
          <div>
            <Link
              color="text.primary"
              component={RouterLink}
              href={paths.dashboard.mf_questions.list}
              sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
              variant="subtitle2"
            >
              <ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
              {t('title')}
            </Link>
          </div>
          <div>
            <Typography variant="h4">{t('create.title')}</Typography>
          </div>
        </Stack>
        <MfQuestionCreateForm />
      </Stack>
    </Box>
  );
}
</file>

<file path="questions/edit/[cat_id]/_PROMPT.md">
# task

## instruction

with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/_helloworld/page.tsx`

with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/lesson_types/edit/[typeId]/page.tsx`

please modify `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/lesson_categories/edit/page.tsx`

please draft a tsx for showing error to user thanks,
</file>

<file path="questions/edit/[cat_id]/page.tsx">
'use client';

import * as React from 'react';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import { MfQuestionEditForm } from '@/components/dashboard/mf/questions/mf-question-edit-form';

export default function Page(): React.JSX.Element {
  const { t } = useTranslation(['lp_questions']);

  React.useEffect(() => {
    // console.log('helloworld');
  }, []);

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack spacing={3}>
          <div>
            <Link
              color="text.primary"
              component={RouterLink}
              href={paths.dashboard.mf_questions.list}
              sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
              variant="subtitle2"
            >
              <ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
              {t('edit.title')}
            </Link>
          </div>
          <div>
            <Typography variant="h4">{t('edit.title')}</Typography>
          </div>
        </Stack>
        <MfQuestionEditForm />
      </Stack>
    </Box>
  );
}
</file>

<file path="questions/lp-categories-sample-data.tsx">
import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type';

export const LpCategoriesSampleData = [
  {
    id: 'USR-005',
    name: 'Fran Perez',
    avatar: '/assets/avatar-5.png',
    email: 'fran.perez@domain.com',
    phone: '(815) 704-0045',
    quota: 50,
    status: 'active',
    createdAt: dayjs().subtract(1, 'hour').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-004',
    name: 'Penjani Inyene',
    avatar: '/assets/avatar-4.png',
    email: 'penjani.inyene@domain.com',
    phone: '(803) 937-8925',
    quota: 100,
    status: 'active',
    createdAt: dayjs().subtract(3, 'hour').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-003',
    name: 'Carson Darrin',
    avatar: '/assets/avatar-3.png',
    email: 'carson.darrin@domain.com',
    phone: '(715) 278-5041',
    quota: 10,
    status: 'blocked',
    createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-002',
    name: 'Siegbert Gottfried',
    avatar: '/assets/avatar-2.png',
    email: 'siegbert.gottfried@domain.com',
    phone: '(603) 766-0431',
    quota: 0,
    status: 'pending',
    createdAt: dayjs().subtract(7, 'hour').subtract(1, 'day').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
  {
    id: 'USR-001',
    name: 'Miron Vitold',
    avatar: '/assets/avatar-1.png',
    email: 'miron.vitold@domain.com',
    phone: '(425) 434-5535',
    quota: 50,
    status: 'active',
    createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(),
    collectionId: '0000000001',
    cat_name: '',
    pos: 99,
    visible: 'visible',
    lesson_id: 'lid_00001',
    description: '',
    remarks: '',
  },
] satisfies LessonCategory[];
</file>

<file path="questions/page.tsx">
'use client';

// RULES:
// contains list page for lp_questions (QuizLPQuestions)
// contain definition to collection only
//
import * as React from 'react';
import { useRouter } from 'next/navigation';
import { COL_QUIZ_MF_QUESTIONS } from '@/constants';
import { LoadingButton } from '@mui/lab';
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import Divider from '@mui/material/Divider';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import type { ListResult, RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next';

import { paths } from '@/paths';
import isDevelopment from '@/lib/check-is-development';
import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error';
import { defaultMfQuestion } from '@/components/dashboard/mf/questions/_constants';
import { MfQuestionsFilters } from '@/components/dashboard/mf/questions/mf-questions-filters';
import type { Filters } from '@/components/dashboard/mf/questions/mf-questions-filters';
import { MfQuestionsPagination } from '@/components/dashboard/mf/questions/mf-questions-pagination';
import { MfQuestionsSelectionProvider } from '@/components/dashboard/mf/questions/mf-questions-selection-context';
import { MfQuestionsTable } from '@/components/dashboard/mf/questions/mf-questions-table';
import type { MfQuestion } from '@/components/dashboard/mf/questions/type';
import FormLoading from '@/components/loading';

export default function Page({ searchParams }: PageProps): React.JSX.Element {
  const { t } = useTranslation(['mf_question']);
  const { email, phone, sortDir, status, name, visible, type } = searchParams;
  const router = useRouter();
  const [lessonQuestionsData, setLessonCategoriesData] = React.useState<MfQuestion[]>([]);
  //

  const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
  const [showLoading, setShowLoading] = React.useState<boolean>(true);
  const [showError, setShowError] = React.useState({ show: false, detail: '' });
  //
  const [rowsPerPage, setRowsPerPage] = React.useState<number>(5);
  const [f, setF] = React.useState<MfQuestion[]>([]);
  const [currentPage, setCurrentPage] = React.useState<number>(0);
  const [recordCount, setRecordCount] = React.useState<number>(0);
  const [listOption, setListOption] = React.useState({});
  const [listSort, setListSort] = React.useState({});

  //
  const sortedLessonCategories = applySort(lessonQuestionsData, sortDir);
  const filteredLessonCategories = applyFilters(sortedLessonCategories, { email, phone, status });

  const reloadRows = async (): Promise<void> => {
    try {
      const models: ListResult<RecordModel> = await pb
        .collection(COL_QUIZ_MF_QUESTIONS)
        .getList(currentPage + 1, rowsPerPage, listOption);
      const { items, totalItems } = models;
      const tempLessonTypes: MfQuestion[] = items.map((lt) => {
        return { ...defaultMfQuestion, ...lt };
      });

      setLessonCategoriesData(tempLessonTypes);
      setRecordCount(totalItems);
      setF(tempLessonTypes);
      // console.log({ currentPage, f });
    } catch (error) {
      //
      logger.error(error);
      setShowError({
        //
        show: true,
        detail: JSON.stringify(error, null, 2),
      });
    } finally {
      setShowLoading(false);
    }
  };

  const [lastListOption, setLastListOption] = React.useState({});
  const isFirstRun = React.useRef(false);
  React.useEffect(() => {
    if (!isFirstRun.current) {
      isFirstRun.current = true;
    } else {
      if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
        // reset page number as tab changes
        setLastListOption(listOption);
        setCurrentPage(0);
        void reloadRows();
      } else {
        void reloadRows();
      }
    }
  }, [currentPage, rowsPerPage, listOption]);

  React.useEffect(() => {
    let tempFilter = [],
      tempSortDir = '';

    if (visible) {
      tempFilter.push(`visible = "${visible}"`);
    }

    if (sortDir) {
      tempSortDir = `-created`;
    }

    if (name) {
      tempFilter.push(`name ~ "%${name}%"`);
    }

    if (type) {
      tempFilter.push(`type ~ "%${type}%"`);
    }

    let preFinalListOption = {};
    if (tempFilter.length > 0) {
      preFinalListOption = { filter: tempFilter.join(' && ') };
    }
    if (tempSortDir.length > 0) {
      preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
    }
    setListOption(preFinalListOption);
    // setListOption({
    //   filter: tempFilter.join(' && '),
    //   sort: tempSortDir,
    //   //
    // });
  }, [visible, sortDir, name, type]);

  // return <>helloworld</>;

  if (showLoading) return <FormLoading />;

  if (showError.show)
    return (
      <ErrorDisplay
        message={t('error.unable-to-process-request')}
        code={-1}
        details={showError.detail}
      />
    );

  return (
    <Box
      sx={{
        maxWidth: 'var(--Content-maxWidth)',
        m: 'var(--Content-margin)',
        p: 'var(--Content-padding)',
        width: 'var(--Content-width)',
      }}
    >
      <Stack spacing={4}>
        <Stack
          direction={{ xs: 'column', sm: 'row' }}
          spacing={3}
          sx={{ alignItems: 'flex-start' }}
        >
          <Box sx={{ flex: '1 1 auto' }}>
            <Typography variant="h4">{t('list.title')}</Typography>
          </Box>
          <Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
            <LoadingButton
              loading={isLoadingAddPage}
              onClick={(): void => {
                setIsLoadingAddPage(true);
                router.push(paths.dashboard.lp_questions.create);
              }}
              startIcon={<PlusIcon />}
              variant="contained"
            >
              {t('list.add')}
            </LoadingButton>
          </Box>
        </Stack>
        <MfQuestionsSelectionProvider lessonQuestions={f}>
          <Card>
            <MfQuestionsFilters
              filters={{ email, phone, status, name, visible, type }}
              fullData={lessonQuestionsData}
              sortDir={sortDir}
            />
            <Divider />
            <Box sx={{ overflowX: 'auto' }}>
              <MfQuestionsTable
                reloadRows={reloadRows}
                rows={f}
              />
            </Box>
            <Divider />
            <MfQuestionsPagination
              count={recordCount}
              page={currentPage}
              rowsPerPage={rowsPerPage}
              setPage={setCurrentPage}
              setRowsPerPage={setRowsPerPage}
            />
          </Card>
        </MfQuestionsSelectionProvider>
      </Stack>
      <Box sx={{ display: isDevelopment ? 'block' : 'none' }}>
        <pre>{JSON.stringify(f, null, 2)}</pre>
      </Box>
    </Box>
  );
}

// Sorting and filtering has to be done on the server.

function applySort(row: MfQuestion[], sortDir: 'asc' | 'desc' | undefined): MfQuestion[] {
  return row.sort((a, b) => {
    if (sortDir === 'asc') {
      return a.createdAt.getTime() - b.createdAt.getTime();
    }

    return b.createdAt.getTime() - a.createdAt.getTime();
  });
}

function applyFilters(row: MfQuestion[], { email, phone, status, name, visible }: Filters): MfQuestion[] {
  return row.filter((item) => {
    if (email) {
      if (!item.email?.toLowerCase().includes(email.toLowerCase())) {
        return false;
      }
    }

    if (phone) {
      if (!item.phone?.toLowerCase().includes(phone.toLowerCase())) {
        return false;
      }
    }

    if (status) {
      if (item.status !== status) {
        return false;
      }
    }

    if (name) {
      if (!item.name?.toLowerCase().includes(name.toLowerCase())) {
        return false;
      }
    }

    if (visible) {
      if (!item.visible?.toLowerCase().includes(visible.toLowerCase())) {
        return false;
      }
    }

    return true;
  });
}

interface PageProps {
  searchParams: {
    email?: string;
    phone?: string;
    sortDir?: 'asc' | 'desc';
    status?: string;
    name?: string;
    visible?: string;
    type?: string;
    //
  };
}
</file>

</files>