diff --git a/src/pages/articles/Articles.tsx b/src/pages/articles/Articles.tsx index 1d41c349..1cb861e6 100644 --- a/src/pages/articles/Articles.tsx +++ b/src/pages/articles/Articles.tsx @@ -16,6 +16,7 @@ import { IArticle } from '@/types/article.types'; import { getArticleArticlesSelector } from '@/redux/reducers/article.reducer'; import { createArticle, deleteArticle, goToArticle } from '@/redux/actions/article.action'; import Head from '@/components/Head'; +import { getUserFullName } from '@/utils/user.utils'; const Articles = () => { const articles = useSelector(getArticleArticlesSelector); @@ -29,9 +30,9 @@ const Articles = () => { dispatch(createArticle(values)); } - const handleDelete = (id: string) => { - dispatch(deleteArticle(id)); - } + // const handleDelete = (id: string) => { + // dispatch(deleteArticle(id)); + // } const handlePreview = (id: string) => { navigate(goToArticle(id)); @@ -60,9 +61,9 @@ const Articles = () => { {article.objectId} {article.title} - {/* + {article.has("author") ? getUserFullName(article.get("author")) : "-"} - */} + handlePreview(article.objectId)}> @@ -70,9 +71,9 @@ const Articles = () => { {/* handleEdit(article.id)}> */} - handleDelete(article.objectId)}> + {/* handleDelete(article.objectId)}> - + */} ))} diff --git a/src/pages/estimates/Estimate.tsx b/src/pages/estimates/Estimate.tsx new file mode 100644 index 00000000..c2b1b241 --- /dev/null +++ b/src/pages/estimates/Estimate.tsx @@ -0,0 +1,51 @@ +import { useTranslation } from 'react-i18next'; +import { useState } from 'react'; +import Head from '@/components/Head'; +import Dialog from '@/components/Dialog'; +import AddFab from '@/components/AddFab'; +import EstimateForm from './EstimateForm'; +import { EstimateInput } from '@/types/estimate.type'; +import { SubmitHandler } from 'react-hook-form'; +import { useDispatch } from 'react-redux'; +import { createEstimate } from '@/redux/actions/estimate.action'; + +const ESTIMATE_FORM_ID = 'estimate-form-id'; + +const Estimate = () => { + const { t } = useTranslation(); + + const dispatch = useDispatch(); + + + const [openFormDialog, setOpenFormDialog] = useState(false); + + const onSubmitHandler: SubmitHandler = values => { + dispatch(createEstimate(values)); + }; + + const toggleDialog = () => setOpenFormDialog(!openFormDialog); + + return ( +
+ +

Estimates

+ + + + +
+ ); +} + +export default Estimate; diff --git a/src/pages/estimates/Estimates.tsx b/src/pages/estimates/Estimates.tsx index 1d21ba90..185b0d22 100644 --- a/src/pages/estimates/Estimates.tsx +++ b/src/pages/estimates/Estimates.tsx @@ -1,49 +1,92 @@ +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Paper from '@mui/material/Paper'; +import { FiPlus, FiTrash2 , FiEye } from "react-icons/fi"; + +import { Fab, IconButton } from '@mui/material'; +import { useNavigate } from '@tanstack/react-router'; +import { useDispatch, useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; -import { useState } from 'react'; import Head from '@/components/Head'; -import Dialog from '@/components/Dialog'; -import AddFab from '@/components/AddFab'; -import EstimateForm from './EstimateForm'; -import { EstimateInput } from '@/types/estimate.type'; -import { SubmitHandler } from 'react-hook-form'; -import { useDispatch } from 'react-redux'; -import { createEstimate } from '@/redux/actions/estimate.action'; - -const ESTIMATE_FORM_ID = 'estimate-form-id'; +import { getEstimateEstimatesSelector } from '@/redux/reducers/estimate.reducer'; +import { IEstimate } from '@/types/estimate.type'; +import { loadEstimates, onEstimatesEnter } from '@/redux/actions/estimate.action'; const Estimates = () => { - const { t } = useTranslation(); + const estimates = useSelector(getEstimateEstimatesSelector); + // const navigate = useNavigate(); + const { t } = useTranslation(); const dispatch = useDispatch(); + + // const handleAddEstimate = async () => { + // const values = { title: `Article ${uid}` }; + // dispatch(createEstimate(values)); + // } + const getEstimate = async () => { + // const values = { url: `Estimate ${uid}` }; + dispatch(loadEstimates()); - const [openFormDialog, setOpenFormDialog] = useState(false); + } - const onSubmitHandler: SubmitHandler = values => { - dispatch(createEstimate(values)); - }; + // const handleDelete = (id: string) => { + // dispatch(deleteArticle(id)); + // } - const toggleDialog = () => setOpenFormDialog(!openFormDialog); + // const handlePreview = (id: string) => { + // navigate(goToArticle(id)); + // } return (
- -

Estimates

- - - - + + + + + + Id + Title + {/* Author */} + Actions + + + + {estimates.map((estimate: IEstimate, index: number) => ( + + + {estimate.objectId} + + + {estimate.url} + + + {/* handlePreview(article.objectId)}> + + */} + {/* handleEdit(article.id)}> + + */} + {/* handleDelete(article.objectId)}> + + */} + + + ))} + +
+
+ {/* */} + {/* + + */}
); } diff --git a/src/pages/users/Users.tsx b/src/pages/users/Users.tsx index 0710a82e..4ce6a795 100644 --- a/src/pages/users/Users.tsx +++ b/src/pages/users/Users.tsx @@ -131,7 +131,7 @@ const Users = () => { } const onSendEmailFormSubmit = async (values: SendEmailInput) => { - console.log('values: ', values); + // console.log('values: ', values); if (!selectedUser) return; await dispatch(sendEmailToUser(selectedUser, values)); handleCloseDialog(); diff --git a/src/redux/actions/estimate.action.ts b/src/redux/actions/estimate.action.ts index 8ace7e20..99cf9d22 100644 --- a/src/redux/actions/estimate.action.ts +++ b/src/redux/actions/estimate.action.ts @@ -1,15 +1,31 @@ -import Parse from "parse"; +import Parse, { Attributes } from "parse"; import { PATH_NAMES } from "@/utils/pathnames"; import { setValues } from "@/utils/parse.utils"; import { actionWithLoader } from "@/utils/app.utils"; import { AppDispatch } from "../store"; import { setMessageSlice } from "../reducers/app.reducer"; import i18n from "@/config/i18n"; +import { addEstimateToEstimateSlice, loadEstimatesSlice } from "../reducers/estimate.reducer"; +// import { ParseResult } from "@/types/util.type"; const Estimate = Parse.Object.extend("Estimate"); const ESTIMATE_PROPERTIES = new Set(['url']); +export const loadEstimates = (): any => { + return actionWithLoader(async (dispatch: AppDispatch): Promise => { + // user from BO + const result: any = await new Parse.Query(Estimate) + .withCount() + .notEqualTo('deleted', true) + .find(); + + const estimates = result.results.map((estimate: Attributes) => estimate.toJSON()); + + dispatch(loadEstimatesSlice(estimates)); + }); +}; + export const createEstimate = (values: any): any => { return actionWithLoader(async (dispatch: AppDispatch): Promise => { const estimate = new Estimate() @@ -20,7 +36,15 @@ export const createEstimate = (values: any): any => { const savedEstimate = await estimate.save(); dispatch(setMessageSlice(i18n.t('common:estimateCreatedSuccessfully'))); - return savedEstimate; + dispatch(addEstimateToEstimateSlice((savedEstimate as Attributes).toJSON())); + // return savedEstimate; + }); +}; + +export const onEstimatesEnter = (): any => { + return actionWithLoader(async (dispatch: AppDispatch): Promise => { + + dispatch(loadEstimates()); }); }; diff --git a/src/redux/reducers/estimate.reducer.ts b/src/redux/reducers/estimate.reducer.ts new file mode 100644 index 00000000..289b3dc6 --- /dev/null +++ b/src/redux/reducers/estimate.reducer.ts @@ -0,0 +1,43 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { IEstimate, IEstimateState } from '@/types/estimate.type'; + +const initialState: IEstimateState = { + loading: false, + estimate: null, + estimates: [], +}; + +export const estimate = createSlice({ + name: 'estimate', + initialState, + reducers: { + addEstimateToEstimateSlice: (state: IEstimateState, action: PayloadAction) => { + state.estimates = [...state.estimates, action.payload]; + }, + loadEstimatesSlice: (state: IEstimateState, action: PayloadAction) => { + state.estimates = action.payload; + } + }, +}); + +export const { + addEstimateToEstimateSlice, + loadEstimatesSlice +} = estimate.actions; + +// ---------------------------------------------- // +// ------------------ SELECTOR ------------------ // +// ---------------------------------------------- // +// NOTE: do not use RootState as state type to avoid import circular dependencies (from store.ts) +// we can not commit to git if there is circular dependencies +// export const getArticleSelector = (state: Record): IArticleState => state.article; +// export const getArticleArticleSelector = (state: Record): IArticle => state.article.article; +// export const getArticleLoadingSelector = (state: Record): boolean => state.article.loading; +export const getEstimateEstimatesSelector = (state: Record): IEstimate[] => state.estimate.estimates; +console.log('getEstimateEstimatesSelector------- : ', getEstimateEstimatesSelector ); + + +// export const getArticleCountSelector = (state: Record): number => state.article.count; + + +export default estimate.reducer; diff --git a/src/redux/store.ts b/src/redux/store.ts index 5c6353ed..1dfbbb5e 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -7,12 +7,14 @@ import appReducer from './reducers/app.reducer'; import roleReducer from './reducers/role.reducer'; import settingsReducer from './reducers/settings.reducer'; import userReducer from './reducers/user.reducer'; +import estimateReducer from './reducers/estimate.reducer'; import articleReducer from './reducers/article.reducer'; const reducers = { app: appReducer, user: userReducer, article: articleReducer, + estimate: estimateReducer, settings: settingsReducer, role: roleReducer, }; diff --git a/src/routes/protected/estimate.routes.tsx b/src/routes/protected/estimate.routes.tsx index 0f8b342a..418d2cea 100644 --- a/src/routes/protected/estimate.routes.tsx +++ b/src/routes/protected/estimate.routes.tsx @@ -2,7 +2,10 @@ import { Outlet, createRoute } from "@tanstack/react-router"; import { privateLayout } from "./private.routes"; import { PATH_NAMES } from "@/utils/pathnames"; +import { onEnter } from "@/redux/actions/app.action"; import Estimates from "@/pages/estimates/Estimates"; +import Estimate from "@/pages/estimates/Estimate"; +import { onEstimatesEnter } from "@/redux/actions/estimate.action"; export const estimatesLayout = createRoute({ getParentRoute: () => privateLayout, @@ -12,11 +15,18 @@ export const estimatesLayout = createRoute({ export const estimatesRoute = createRoute({ getParentRoute: () => estimatesLayout, - // beforeLoad: onEnter(onArticlesEnter), + beforeLoad: onEnter(onEstimatesEnter), component: Estimates, path: "/", }); -const estimateRoutes = [estimatesRoute]; +export const estimateRoute = createRoute({ + getParentRoute: () => estimatesLayout, + // beforeLoad: onEnter(onArticleEnter), + component: Estimate, + path: "$id", +}); + +const estimateRoutes = [estimatesRoute, estimateRoute]; export default estimateRoutes; diff --git a/src/types/estimate.type.ts b/src/types/estimate.type.ts index 5c227d29..d73bbb77 100644 --- a/src/types/estimate.type.ts +++ b/src/types/estimate.type.ts @@ -1,4 +1,18 @@ import { z } from "zod"; +import { Attributes } from "parse"; import { estimateSchema } from "@/validations/estimate.validation"; export type EstimateInput = z.infer; + +export interface IEstimate extends Attributes { + id: string; + url: string; + updatedAt?: string; + createdAt?: string; +} + +export interface IEstimateState { + loading: boolean; + estimate: IEstimate | null; + estimates: IEstimate[]; +} \ No newline at end of file diff --git a/src/types/util.type.ts b/src/types/util.type.ts index f5789251..376c1308 100644 --- a/src/types/util.type.ts +++ b/src/types/util.type.ts @@ -1,5 +1,6 @@ import { Dayjs } from "dayjs"; import { ISelectOption } from "./app.type"; +import { Attributes } from "parse"; export type DateType = string | number | Date | Dayjs | null | undefined; @@ -21,3 +22,8 @@ export interface ICreatableSelectOption extends Partial { inputValue?: any; disabled?: boolean; } + +export interface ParseResult { + results: Attributes[]; +} +