Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions src/pages/articles/Articles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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));
Expand All @@ -46,7 +47,7 @@ const Articles = () => {
<TableRow>
<TableCell>Id</TableCell>
<TableCell align="right">Title</TableCell>
{/* <TableCell align="right">Author</TableCell> */}
<TableCell align="right">Author</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
Expand All @@ -60,19 +61,19 @@ const Articles = () => {
{article.objectId}
</TableCell>
<TableCell align="right">{article.title}</TableCell>
{/* <TableCell component="th" scope="row">
<TableCell component="th" scope="row">
{article.has("author") ? getUserFullName(article.get("author")) : "-"}
</TableCell> */}
</TableCell>
<TableCell align="right">
<IconButton color="info" onClick={() => handlePreview(article.objectId)}>
<FiEye />
</IconButton>
{/* <IconButton color="info" onClick={() => handleEdit(article.id)}>
<FiEdit2 />
</IconButton> */}
<IconButton color="error" onClick={() => handleDelete(article.objectId)}>
{/* <IconButton color="error" onClick={() => handleDelete(article.objectId)}>
<FiTrash2 />
</IconButton>
</IconButton> */}
</TableCell>
</TableRow>
))}
Expand Down
54 changes: 54 additions & 0 deletions src/pages/estimates/Estimate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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, deleteEstimate, goToEstimates } from '@/redux/actions/estimate.action';
import { useNavigate } from '@tanstack/react-router';

const ESTIMATE_FORM_ID = 'estimate-form-id';

// eslint-disable-next-line react-hooks/rules-of-hooks

const Estimate = () => {
const { t } = useTranslation();

const dispatch = useDispatch();


const [openFormDialog, setOpenFormDialog] = useState<boolean>(false);

const onSubmitHandler: SubmitHandler<EstimateInput> = values => {
dispatch(createEstimate(values));
};

const toggleDialog = () => setOpenFormDialog(!openFormDialog);

return (
<div>
<Head title={t('estimates')} />
<h1>Estimates</h1>
<AddFab onClick={toggleDialog} />
<Dialog
maxWidth="sm"
fullWidth
primaryButtonText={t('save')}
title={t('createEstimate')}
open={openFormDialog}
formId={ESTIMATE_FORM_ID}
toggle={toggleDialog}
>
<EstimateForm
formId={ESTIMATE_FORM_ID}
onSubmit={onSubmitHandler}
/>
</Dialog>
</div>
);
}

export default Estimate;
87 changes: 50 additions & 37 deletions src/pages/estimates/Estimates.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,62 @@
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 { useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { useState } from 'react';
import { FiTrash2 } from 'react-icons/fi';
import { IconButton } from '@mui/material';
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 { deleteEstimate, goToEstimates } from '@/redux/actions/estimate.action';

const Estimates = () => {
const { t } = useTranslation();

const estimates = useSelector(getEstimateEstimatesSelector);
const dispatch = useDispatch();

const { t } = useTranslation();

const [openFormDialog, setOpenFormDialog] = useState<boolean>(false);

const onSubmitHandler: SubmitHandler<EstimateInput> = values => {
dispatch(createEstimate(values));
};

const toggleDialog = () => setOpenFormDialog(!openFormDialog);

const handleDelete = async (id: string) => {
await dispatch(deleteEstimate(id));
}
return (
<div>
<Head title={t('estimates')} />
<h1>Estimates</h1>
<AddFab onClick={toggleDialog} />
<Dialog
maxWidth="sm"
fullWidth
primaryButtonText={t('save')}
title={t('createEstimate')}
open={openFormDialog}
formId={ESTIMATE_FORM_ID}
toggle={toggleDialog}
>
<EstimateForm
formId={ESTIMATE_FORM_ID}
onSubmit={onSubmitHandler}
/>
</Dialog>
<Head title={t('articles')} />
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Id</TableCell>
<TableCell align="right">Title</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{estimates.map((estimate: IEstimate, index: number) => (
<TableRow
key={estimate.objectId + index}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row">
{estimate.objectId}
</TableCell>
<TableCell component="th" scope="row">
{estimate.url}
</TableCell>
<TableCell align="right">
<IconButton color="error" onClick={() => handleDelete(estimate.objectId)}>
<FiTrash2 />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/users/Users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion src/redux/actions/app.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export const changeSettings = (values: ISettingsInput): any => {
* @param routeParams
* @returns
*/
export const onEnter = (onEnterAction: (dispatch: AppDispatch, getState?: () => RootState) => AppThunkAction) => (routeParams: any) => {
export const onEnter = (onEnterAction: (dispatch: AppDispatch, getState?: () => RootState) => AppThunkAction) => (routeParams: any) => {
// get store from context (passed in RouterProvider)
const { store } = routeParams.context;
if (!store) return;
Expand Down
59 changes: 56 additions & 3 deletions src/redux/actions/estimate.action.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,45 @@
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 { AppDispatch, RootState } from "../store";
import { setMessageSlice } from "../reducers/app.reducer";
import i18n from "@/config/i18n";
import { addEstimateToEstimateSlice, deleteEstimateFromEstimatesSlice, loadEstimatesSlice } from "../reducers/estimate.reducer";

const Estimate = Parse.Object.extend("Estimate");

const ESTIMATE_PROPERTIES = new Set(['url']);

export const getEstimate = async (id: string): Promise<Parse.Object | undefined> => {
const estimate = await new Parse.Query(Estimate)
.equalTo('objectId', id)
.include(["comments"])
.notEqualTo('deleted', true)
.first();

console.log("id estimate ---------:", id);

if (!estimate) {
throw new Error("Estimate not found");
}
return estimate;
}

export const loadEstimates = (): any => {
return actionWithLoader(async (dispatch: AppDispatch): Promise<void> => {
// 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<void | undefined> => {
const estimate = new Estimate()
Expand All @@ -20,7 +50,30 @@ 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()));
});
};

export const deleteEstimate = (id: string,): any => {
return actionWithLoader(async (dispatch: AppDispatch): Promise<void | undefined> => {
const estimate = await getEstimate(id);

if (!estimate) return;

estimate.set('deleted', true);
const deletedEstimate = await estimate.save();

dispatch(deleteEstimateFromEstimatesSlice(deletedEstimate.id));

dispatch(setMessageSlice('Estimate deleted successfully'));
});
};


export const onEstimatesEnter = (): any => {
return actionWithLoader(async (dispatch: AppDispatch): Promise<void> => {

dispatch(loadEstimates());
});
};

Expand Down
46 changes: 46 additions & 0 deletions src/redux/reducers/estimate.reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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<IEstimate>) => {
state.estimates = [...state.estimates, action.payload];
},
loadEstimatesSlice: (state: IEstimateState, action: PayloadAction<IEstimate[]>) => {
state.estimates = action.payload;
},
deleteEstimateFromEstimatesSlice: (state: IEstimateState, action: PayloadAction<string>) => {
state.estimates = state.estimates.filter((estimate: IEstimate) => estimate.objectId !== action.payload);
},
},
});

export const {
addEstimateToEstimateSlice,
loadEstimatesSlice,
deleteEstimateFromEstimatesSlice
} = 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<string, any>): IArticleState => state.article;
// export const getArticleArticleSelector = (state: Record<string, any>): IArticle => state.article.article;
// export const getArticleLoadingSelector = (state: Record<string, any>): boolean => state.article.loading;
export const getEstimateEstimatesSelector = (state: Record<string, any>): IEstimate[] => state.estimate.estimates;


// export const getArticleCountSelector = (state: Record<string, any>): number => state.article.count;


export default estimate.reducer;
2 changes: 2 additions & 0 deletions src/redux/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
Loading