mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
feat: Attachment files system.
This commit is contained in:
@@ -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 });
|
||||
|
||||
@@ -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);
|
||||
@@ -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]);
|
||||
|
||||
@@ -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,
|
||||
}}/>
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -126,7 +126,8 @@ function ProfitLossSheetTable({
|
||||
data={profitLossTableRows}
|
||||
onFetchData={handleFetchData}
|
||||
expanded={expandedRows}
|
||||
rowClassNames={rowClassNames} />
|
||||
rowClassNames={rowClassNames}
|
||||
noInitialFetch={true} />
|
||||
</FinancialSheet>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user