feat: Attachment files system.

This commit is contained in:
Ahmed Bouhuolia
2020-05-04 05:11:44 +02:00
parent a807cf6bb8
commit 7f06e3781c
35 changed files with 757 additions and 179 deletions

View File

@@ -6,7 +6,7 @@ import {
import { FormattedList } from 'react-intl';
export default function MakeJournalEntriesFooter({
formik,
formik: { isSubmitting },
onSubmitClick,
onCancelClick,
}) {
@@ -14,7 +14,7 @@ export default function MakeJournalEntriesFooter({
<div>
<div class="form__floating-footer">
<Button
disabled={formik.isSubmitting}
disabled={isSubmitting}
intent={Intent.PRIMARY}
name={'save'}
onClick={() => {
@@ -24,7 +24,7 @@ export default function MakeJournalEntriesFooter({
</Button>
<Button
disabled={formik.isSubmitting}
disabled={isSubmitting}
intent={Intent.PRIMARY}
className={'ml1'}
name={'save_and_new'}
@@ -35,7 +35,7 @@ export default function MakeJournalEntriesFooter({
</Button>
<Button
disabled={formik.isSubmitting}
disabled={isSubmitting}
className={'button-secondary ml1'}
onClick={() => {
onSubmitClick({ publish: false, redirect: false });

View File

@@ -1,5 +1,10 @@
import React, {useMemo, useState, useEffect, useCallback} from 'react';
import React, {useMemo, useState, useEffect, useRef, useCallback} from 'react';
import * as Yup from 'yup';
import {
ProgressBar,
Classes,
Intent,
} from '@blueprintjs/core';
import MakeJournalEntriesHeader from './MakeJournalEntriesHeader';
import MakeJournalEntriesFooter from './MakeJournalEntriesFooter';
import MakeJournalEntriesTable from './MakeJournalEntriesTable';
@@ -7,12 +12,18 @@ import {useFormik} from "formik";
import MakeJournalEntriesConnect from 'connectors/MakeJournalEntries.connect';
import AccountsConnect from 'connectors/Accounts.connector';
import DashboardConnect from 'connectors/Dashboard.connector';
import {compose} from 'utils';
import {compose, saveFilesInAsync} from 'utils';
import moment from 'moment';
import AppToaster from 'components/AppToaster';
import {pick} from 'lodash';
import Dragzone from 'components/Dragzone';
import MediaConnect from 'connectors/Media.connect';
import classNames from 'classnames';
import ManualJournalsConnect from 'connectors/ManualJournals.connect';
import useMedia from 'hooks/useMedia';
function MakeJournalEntriesForm({
requestSubmitMedia,
requestMakeJournalEntries,
requestEditManualJournal,
changePageTitle,
@@ -20,7 +31,21 @@ function MakeJournalEntriesForm({
editJournal,
onFormSubmit,
onCancelForm,
requestDeleteMedia,
manualJournalsItems
}) {
const { setFiles, saveMedia, deletedFiles, setDeletedFiles, deleteMedia } = useMedia({
saveCallback: requestSubmitMedia,
deleteCallback: requestDeleteMedia,
});
const handleDropFiles = useCallback((_files) => {
setFiles(_files.filter((file) => file.uploaded === false));
}, []);
const savedMediaIds = useRef([]);
const clearSavedMediaIds = () => { savedMediaIds.current = []; }
useEffect(() => {
if (editJournal && editJournal.id) {
changePageTitle('Edit Journal');
@@ -61,7 +86,7 @@ function MakeJournalEntriesForm({
note: '',
}), []);
const initialValues = useMemo(() => ({
const defaultInitialValues = useMemo(() => ({
journal_number: '',
date: moment(new Date()).format('YYYY-MM-DD'),
description: '',
@@ -74,20 +99,33 @@ function MakeJournalEntriesForm({
],
}), [defaultEntry]);
const initialValues = useMemo(() => ({
...(editJournal) ? {
...pick(editJournal, Object.keys(defaultInitialValues)),
entries: editJournal.entries.map((entry) => ({
...pick(entry, Object.keys(defaultEntry)),
})),
} : {
...defaultInitialValues,
}
}), [editJournal, defaultInitialValues, defaultEntry]);
const initialAttachmentFiles = useMemo(() => {
return editJournal && editJournal.media
? editJournal.media.map((attach) => ({
preview: attach.attachment_file,
uploaded: true,
metadata: { ...attach },
})) : [];
}, [editJournal]);
const formik = useFormik({
enableReinitialize: true,
validationSchema,
initialValues: {
...(editJournal) ? {
...pick(editJournal, Object.keys(initialValues)),
entries: editJournal.entries.map((entry) => ({
...pick(entry, Object.keys(defaultEntry)),
}))
} : {
...initialValues,
}
...initialValues,
},
onSubmit: (values, actions) => {
onSubmit: async (values, actions) => {
const entries = values.entries.filter((entry) => (
(entry.credit || entry.debit)
));
@@ -99,6 +137,7 @@ function MakeJournalEntriesForm({
const totalCredit = getTotal('credit');
const totalDebit = getTotal('debit');
// Validate the total credit should be eqials total debit.
if (totalCredit !== totalDebit) {
AppToaster.show({
message: 'credit_and_debit_not_equal',
@@ -108,29 +147,51 @@ function MakeJournalEntriesForm({
}
const form = { ...values, status: payload.publish, entries };
if (editJournal && editJournal.id) {
requestEditManualJournal(editJournal.id, form)
.then((response) => {
AppToaster.show({
message: 'manual_journal_has_been_edited',
});
actions.setSubmitting(false);
saveInvokeSubmit({ action: 'update', ...payload });
}).catch((error) => {
actions.setSubmitting(false);
});
} else {
requestMakeJournalEntries(form)
.then((response) => {
AppToaster.show({
message: 'manual_journal_has_been_submit',
});
actions.setSubmitting(false);
saveInvokeSubmit({ action: 'new', ...payload });
}).catch((error) => {
actions.setSubmitting(false);
});
}
const saveJournal = (mediaIds) => new Promise((resolve, reject) => {
const requestForm = { ...form, media_ids: mediaIds };
if (editJournal && editJournal.id) {
requestEditManualJournal(editJournal.id, requestForm)
.then((response) => {
AppToaster.show({
message: 'manual_journal_has_been_edited',
});
actions.setSubmitting(false);
saveInvokeSubmit({ action: 'update', ...payload });
clearSavedMediaIds([]);
resolve(response);
}).catch((error) => {
actions.setSubmitting(false);
reject(error);
});
} else {
requestMakeJournalEntries(requestForm)
.then((response) => {
AppToaster.show({
message: 'manual_journal_has_been_submit',
});
actions.setSubmitting(false);
saveInvokeSubmit({ action: 'new', ...payload });
clearSavedMediaIds();
resolve(response);
}).catch((error) => {
actions.setSubmitting(false);
reject(error);
});
}
});
Promise.all([
saveMedia(),
deleteMedia(),
]).then(([savedMediaResponses]) => {
const mediaIds = savedMediaResponses.map(res => res.data.media.id);
savedMediaIds.current = mediaIds;
return savedMediaResponses;
}).then(() => {
return saveJournal(savedMediaIds.current);
});
},
});
@@ -143,22 +204,45 @@ function MakeJournalEntriesForm({
onCancelForm && onCancelForm(payload);
}, [onCancelForm]);
const handleDeleteFile = useCallback((_deletedFiles) => {
_deletedFiles.forEach((deletedFile) => {
if (deletedFile.uploaded && deletedFile.metadata.id) {
setDeletedFiles([
...deletedFiles, deletedFile.metadata.id,
]);
}
});
}, [setDeletedFiles, deletedFiles]);
return (
<div class="make-journal-entries">
<form onSubmit={formik.handleSubmit}>
<MakeJournalEntriesHeader formik={formik} />
<MakeJournalEntriesTable formik={formik} defaultRow={defaultEntry} />
<MakeJournalEntriesTable
initialValues={initialValues}
formik={formik}
defaultRow={defaultEntry} />
<MakeJournalEntriesFooter
formik={formik}
onSubmitClick={handleSubmitClick}
onCancelClick={handleCancelClick} />
</form>
<Dragzone
initialFiles={initialAttachmentFiles}
onDrop={handleDropFiles}
onDeleteFile={handleDeleteFile}
hint={'Attachments: Maxiumum size: 20MB'} />
</div>
);
}
export default compose(
ManualJournalsConnect,
MakeJournalEntriesConnect,
AccountsConnect,
DashboardConnect,
MediaConnect,
)(MakeJournalEntriesForm);

View File

@@ -1,6 +1,6 @@
import React, {useMemo, useCallback} from 'react';
import { useParams, useHistory } from 'react-router-dom';
import { useAsync } from 'react-use';
import useAsync from 'hooks/async';
import MakeJournalEntriesForm from './MakeJournalEntriesForm';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import DashboardConnect from 'connectors/Dashboard.connector';
@@ -22,6 +22,7 @@ function MakeJournalEntriesPage({
(id) && fetchManualJournal(id),
]);
});
const editJournal = useMemo(() =>
getManualJournal(id) || null,
[getManualJournal, id]);

View File

@@ -1,4 +1,4 @@
import React, {useState, useMemo, useCallback} from 'react';
import React, {useState, useMemo, useEffect, useCallback} from 'react';
import {
Button,
Intent,
@@ -74,34 +74,36 @@ const NoteCellRenderer = (chainedComponent) => (props) => {
* Make journal entries table component.
*/
function MakeJournalEntriesTable({
formik,
formik: { errors, values, setFieldValue },
accounts,
onClickRemoveRow,
onClickAddNewRow,
defaultRow,
initialValues,
}) {
const [rows, setRow] = useState([
...formik.values.entries.map((e) => ({ ...e, rowType: 'editor'})),
defaultRow,
defaultRow,
]);
const [rows, setRow] = useState([]);
useEffect(() => {
setRow([
...initialValues.entries.map((e) => ({ ...e, rowType: 'editor'})),
defaultRow,
defaultRow,
])
}, [initialValues, defaultRow])
// Handles update datatable data.
const handleUpdateData = useCallback((rowIndex, columnId, value) => {
const newRows = rows.map((row, index) => {
if (index === rowIndex) {
return {
...rows[rowIndex],
[columnId]: value,
};
return { ...rows[rowIndex], [columnId]: value };
}
return { ...row };
});
setRow(newRows);
formik.setFieldValue('entries', newRows.map(row => ({
setFieldValue('entries', newRows.map(row => ({
...omit(row, ['rowType']),
})));
}, [rows, formik]);
}, [rows, setFieldValue]);
// Handles click remove datatable row.
const handleRemoveRow = useCallback((rowIndex) => {
@@ -109,12 +111,12 @@ function MakeJournalEntriesTable({
const newRows = rows.filter((row, index) => index !== removeIndex);
setRow([ ...newRows ]);
formik.setFieldValue('entries', newRows
setFieldValue('entries', newRows
.filter(row => row.rowType === 'editor')
.map(row => ({ ...omit(row, ['rowType']) })
));
onClickRemoveRow && onClickRemoveRow(removeIndex);
}, [rows, formik, onClickRemoveRow]);
}, [rows, setFieldValue, onClickRemoveRow]);
// Memorized data table columns.
const columns = useMemo(() => [
@@ -196,7 +198,7 @@ function MakeJournalEntriesTable({
rowClassNames={rowClassNames}
payload={{
accounts,
errors: formik.errors.entries || [],
errors: errors.entries || [],
updateData: handleUpdateData,
removeRow: handleRemoveRow,
}}/>

View File

@@ -34,7 +34,7 @@ function BalanceSheet({
await Promise.all([
fetchBalanceSheet({ ...query }),
]);
});
}, false);
// Handle fetch the data of balance sheet.
const handleFetchData = useCallback(() => { fetchHook.execute(); }, [fetchHook]);

View File

@@ -9,9 +9,10 @@ import {
compose,
defaultExpanderReducer,
} from 'utils';
import SettingsConnect from 'connectors/Settings.connect';
function BalanceSheetTable({
companyName,
organizationSettings,
balanceSheetAccounts,
balanceSheetColumns,
balanceSheetQuery,
@@ -110,7 +111,7 @@ function BalanceSheetTable({
return (
<FinancialSheet
companyName={companyName}
companyName={organizationSettings.name}
sheetType={'Balance Sheet'}
fromDate={balanceSheetQuery.from_date}
toDate={balanceSheetQuery.to_date}
@@ -122,8 +123,9 @@ function BalanceSheetTable({
columns={columns}
data={balanceSheetAccounts}
onFetchData={handleFetchData}
expanded={expandedRows}
expandSubRows={true}
expanded={expandedRows} />
noInitialFetch={true} />
</FinancialSheet>
);
}
@@ -131,4 +133,5 @@ function BalanceSheetTable({
export default compose(
BalanceSheetConnect,
BalanceSheetTableConnect,
SettingsConnect,
)(BalanceSheetTable);

View File

@@ -89,7 +89,8 @@ function JournalSheetTable({
data={data}
onFetchData={handleFetchData}
noResults={"This report does not contain any data."}
expanded={expandedRows} />
expanded={expandedRows}
noInitialFetch={true} />
</FinancialSheet>
);
}

View File

@@ -126,7 +126,8 @@ function ProfitLossSheetTable({
data={profitLossTableRows}
onFetchData={handleFetchData}
expanded={expandedRows}
rowClassNames={rowClassNames} />
rowClassNames={rowClassNames}
noInitialFetch={true} />
</FinancialSheet>
);
}