From 23bdade5e7421700c9b34cb587c46a623c256f2f Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 28 Aug 2024 21:28:42 -0500 Subject: [PATCH 1/5] add projectId and qfRoundId to qf data export --- src/services/googleSheets.ts | 2 ++ src/services/projectViewsService.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/services/googleSheets.ts b/src/services/googleSheets.ts index a888a32b6..2bf99186d 100644 --- a/src/services/googleSheets.ts +++ b/src/services/googleSheets.ts @@ -175,6 +175,8 @@ export const addQfRoundDonationsSheetToSpreadsheet = async (params: { 'totalValuesOfUserDonationsAfterAnalysis', 'uniqueUserIdsAfterAnalysis', 'projectOwnerEmail', + 'projectId', + 'qfRoundId', ]; const { rows, qfRoundId } = params; diff --git a/src/services/projectViewsService.ts b/src/services/projectViewsService.ts index b317e8d8c..50532411d 100644 --- a/src/services/projectViewsService.ts +++ b/src/services/projectViewsService.ts @@ -119,6 +119,8 @@ export const getQfRoundActualDonationDetails = async ( row?.totalValuesOfUserDonationsAfterAnalysis?.join('-'), uniqueUserIdsAfterAnalysis: row?.uniqueUserIdsAfterAnalysis?.join('-'), projectOwnerEmail: row?.email, // can be empty for new users + projectId: row?.projectId, + qfRoundId: row?.qfRoundId, }; }); logger.debug( From cc79c2cefa45d351a5f84fe412aadb521cbd0aa9 Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 6 Sep 2024 00:38:51 -0500 Subject: [PATCH 2/5] improve adminjs to import qfround matching and better filters --- .../components/CustomIdFilterComponent.tsx | 28 +++ .../CustomProjectReferenceComponent.tsx | 14 ++ .../CustomProjectReferenceShowComponent.tsx | 20 ++ .../CustomQfRoundMultiUpdateComponent.tsx | 187 ++++++++++++++++++ .../CustomQfRoundReferenceComponent.tsx | 14 ++ .../CustomQfRoundReferenceShowComponent.tsx | 20 ++ src/server/adminJs/tabs/qfRoundHistoryTab.ts | 103 +++++++++- 7 files changed, 382 insertions(+), 4 deletions(-) create mode 100644 src/server/adminJs/tabs/components/CustomIdFilterComponent.tsx create mode 100644 src/server/adminJs/tabs/components/CustomProjectReferenceComponent.tsx create mode 100644 src/server/adminJs/tabs/components/CustomProjectReferenceShowComponent.tsx create mode 100644 src/server/adminJs/tabs/components/CustomQfRoundMultiUpdateComponent.tsx create mode 100644 src/server/adminJs/tabs/components/CustomQfRoundReferenceComponent.tsx create mode 100644 src/server/adminJs/tabs/components/CustomQfRoundReferenceShowComponent.tsx diff --git a/src/server/adminJs/tabs/components/CustomIdFilterComponent.tsx b/src/server/adminJs/tabs/components/CustomIdFilterComponent.tsx new file mode 100644 index 000000000..9c2e852e0 --- /dev/null +++ b/src/server/adminJs/tabs/components/CustomIdFilterComponent.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { FormGroup, Label, Input } from '@adminjs/design-system'; + +const CustomIdFilterComponent = props => { + const { onChange, property, filter } = props; + const handleChange = event => { + onChange(property.path, event.target.value); + }; + + return ( + + + + + ); +}; + +export default CustomIdFilterComponent; diff --git a/src/server/adminJs/tabs/components/CustomProjectReferenceComponent.tsx b/src/server/adminJs/tabs/components/CustomProjectReferenceComponent.tsx new file mode 100644 index 000000000..eb2f71dfd --- /dev/null +++ b/src/server/adminJs/tabs/components/CustomProjectReferenceComponent.tsx @@ -0,0 +1,14 @@ +// components/CustomProjectReferenceListComponent.jsx +import React from 'react'; +import { Link } from '@adminjs/design-system'; + +const CustomProjectReferenceListComponent = props => { + const { record } = props; + const projectId = + record.params.project?.id || record.params.projectId || 'N/A'; + const href = `/admin/resources/Project/records/${projectId}/show`; + + return Project {projectId}; +}; + +export default CustomProjectReferenceListComponent; diff --git a/src/server/adminJs/tabs/components/CustomProjectReferenceShowComponent.tsx b/src/server/adminJs/tabs/components/CustomProjectReferenceShowComponent.tsx new file mode 100644 index 000000000..4fd6baa84 --- /dev/null +++ b/src/server/adminJs/tabs/components/CustomProjectReferenceShowComponent.tsx @@ -0,0 +1,20 @@ +// components/CustomProjectReferenceShowComponent.jsx +import React from 'react'; +import { Link, ValueGroup } from '@adminjs/design-system'; + +const CustomProjectReferenceShowComponent = props => { + const { record } = props; + const projectId = + record.params.project?.id || record.params.projectId || 'N/A'; + const href = `/admin/resources/Project/records/${projectId}/show`; + + return ( + + + {projectId} + + + ); +}; + +export default CustomProjectReferenceShowComponent; diff --git a/src/server/adminJs/tabs/components/CustomQfRoundMultiUpdateComponent.tsx b/src/server/adminJs/tabs/components/CustomQfRoundMultiUpdateComponent.tsx new file mode 100644 index 000000000..02bc3109c --- /dev/null +++ b/src/server/adminJs/tabs/components/CustomQfRoundMultiUpdateComponent.tsx @@ -0,0 +1,187 @@ +// customQfRoundMultiUpdateComponent.js +import React, { useState } from 'react'; +import { Box, Button, Text, DatePicker, Select } from '@adminjs/design-system'; +import { FormGroup, Label, Input } from '@adminjs/design-system'; +import { ApiClient } from 'adminjs'; + +const RecordInput = ({ index, record, updateRecord, removeRecord }) => ( + + + + updateRecord(index, 'projectId', e.target.value)} + required + /> + + + + updateRecord(index, 'qfroundId', e.target.value)} + required + /> + + + + + updateRecord(index, 'matchingFundAmount', e.target.value) + } + required + /> + + + + + updateRecord(index, 'matchingFundPriceUsd', e.target.value) + } + required + /> + + + + + updateRecord(index, 'distributedFundTxHash', e.target.value) + } + /> + + + + + updateRecord(index, 'distributedFundNetwork', e.target.value) + } + /> + + + + updateRecord(index, 'distributedFundTxDate', date)} + /> + + + +); + +const CustomQfRoundMultiUpdateComponent = props => { + const [records, setRecords] = useState([ + { + projectId: '', + qfroundId: '', + matchingFundAmount: '', + matchingFundPriceUsd: '', + matchingFundCurrency: '', + distributedFundTxHash: '', + distributedFundNetwork: '', + distributedFundTxDate: null, + }, + ]); + const [message, setMessage] = useState(''); + + const api = new ApiClient(); + + const addRecord = () => { + setRecords([ + ...records, + { + projectId: '', + qfroundId: '', + matchingFundAmount: '', + matchingFundPriceUsd: '', + matchingFundCurrency: '', + distributedFundTxHash: '', + distributedFundNetwork: '', + distributedFundTxDate: null, + }, + ]); + }; + + const updateRecord = (index, field, value) => { + const updatedRecords = [...records]; + updatedRecords[index][field] = value; + setRecords(updatedRecords); + }; + + const removeRecord = index => { + const updatedRecords = records.filter((_, i) => i !== index); + setRecords(updatedRecords); + }; + + const handleSubmit = async event => { + event.preventDefault(); + setMessage(''); + + try { + const response = await api.resourceAction({ + resourceId: 'QfRoundHistory', + actionName: 'bulkUpdate', + data: { records }, + }); + + if (response.data.notice) { + if (typeof response.data.notice === 'string') { + setMessage(response.data.notice); + } else if (typeof response.data.notice.message === 'string') { + setMessage(response.data.notice.message); + } else { + setMessage('Update successful'); + } + } else { + setMessage('Update successful'); + } + } catch (error) { + setMessage(`Error: ${error.message}`); + } + }; + + return ( + + + Update Multiple QfRoundHistory Records + + {records.map((record, index) => ( + + ))} + + + {message && {message}} + + ); +}; + +export default CustomQfRoundMultiUpdateComponent; diff --git a/src/server/adminJs/tabs/components/CustomQfRoundReferenceComponent.tsx b/src/server/adminJs/tabs/components/CustomQfRoundReferenceComponent.tsx new file mode 100644 index 000000000..5887ed9db --- /dev/null +++ b/src/server/adminJs/tabs/components/CustomQfRoundReferenceComponent.tsx @@ -0,0 +1,14 @@ +// components/CustomQfRoundReferenceListComponent.jsx +import React from 'react'; +import { Link } from '@adminjs/design-system'; + +const CustomQfRoundReferenceListComponent = props => { + const { record } = props; + const qfRoundId = + record.params.qfRound?.id || record.params.qfRoundId || 'N/A'; + const href = `/admin/resources/QfRound/records/${qfRoundId}/show`; + + return QF Round {qfRoundId}; +}; + +export default CustomQfRoundReferenceListComponent; diff --git a/src/server/adminJs/tabs/components/CustomQfRoundReferenceShowComponent.tsx b/src/server/adminJs/tabs/components/CustomQfRoundReferenceShowComponent.tsx new file mode 100644 index 000000000..976247555 --- /dev/null +++ b/src/server/adminJs/tabs/components/CustomQfRoundReferenceShowComponent.tsx @@ -0,0 +1,20 @@ +// components/CustomQfRoundReferenceShowComponent.jsx +import React from 'react'; +import { Link, ValueGroup } from '@adminjs/design-system'; + +const CustomQfRoundReferenceShowComponent = props => { + const { record } = props; + const qfRoundId = + record.params.qfRound?.id || record.params.qfRoundId || 'N/A'; + const href = `/admin/resources/QfRound/records/${qfRoundId}/show`; + + return ( + + + {qfRoundId} + + + ); +}; + +export default CustomQfRoundReferenceShowComponent; diff --git a/src/server/adminJs/tabs/qfRoundHistoryTab.ts b/src/server/adminJs/tabs/qfRoundHistoryTab.ts index d4fadb5cf..680369db2 100644 --- a/src/server/adminJs/tabs/qfRoundHistoryTab.ts +++ b/src/server/adminJs/tabs/qfRoundHistoryTab.ts @@ -2,6 +2,7 @@ import { canAccessQfRoundHistoryAction, ResourceActions, } from '../adminJsPermissions'; +import adminJs from 'adminjs'; import { QfRoundHistory } from '../../../entities/qfRoundHistory'; import { @@ -48,21 +49,57 @@ export const qfRoundHistoryTab = { resource: QfRoundHistory, options: { properties: { - project: { + projectId: { isVisible: { - list: false, + list: true, edit: false, filter: true, show: true, }, + reference: 'Project', + position: 100, + type: 'reference', + custom: { + getValue: record => { + return record.params.project?.id || record.params.projectId; + }, + renderValue: (value, record) => { + return value ? `Project ${value}` : 'N/A'; + }, + }, + components: { + list: adminJs.bundle('./components/CustomProjectReferenceComponent'), + show: adminJs.bundle( + './components/CustomProjectReferenceShowComponent', + ), + filter: adminJs.bundle('./components/CustomIdFilterComponent'), + }, }, - qfRound: { + qfRoundId: { isVisible: { - list: false, + list: true, edit: false, filter: true, show: true, }, + reference: 'QfRound', + position: 101, + type: 'reference', + custom: { + getValue: record => { + return record.params.qfRound?.id || record.params.qfRoundId; + }, + renderValue: (value, record) => { + return value ? `QF Round ${value}` : 'N/A'; + }, + }, + components: { + list: adminJs.bundle('./components/CustomQfRoundReferenceComponent'), + show: adminJs.bundle( + './components/CustomQfRoundReferenceShowComponent', + ), + filter: adminJs.bundle('./components/CustomIdFilterComponent'), + }, }, uniqueDonors: { isVisible: true, @@ -135,6 +172,64 @@ export const qfRoundHistoryTab = { isAccessible: ({ currentAdmin }) => canAccessQfRoundHistoryAction({ currentAdmin }, ResourceActions.SHOW), }, + bulkUpdateQfRound: { + component: adminJs.bundle( + './components/CustomQfRoundMultiUpdateComponent', + ), + handler: async (request, response, context) => { + const { records } = request.payload; + const results: string[] = []; + + for (const record of records) { + const { + projectId, + qfRoundId, + matchingFundAmount, + matchingFundPriceUsd, + matchingFundCurrency, + distributedFundTxHash, + distributedFundNetwork, + distributedFundTxDate, + } = record; + + let existingRecord = await QfRoundHistory.findOne({ + where: { projectId, qfRoundId }, + }); + + const matchingFund = Number(matchingFundAmount); + + if (existingRecord) { + await QfRoundHistory.createQueryBuilder() + .update(QfRoundHistory) + .set({ + matchingFund, + matchingFundAmount, + matchingFundPriceUsd, + matchingFundCurrency, + distributedFundTxHash, + distributedFundNetwork, + distributedFundTxDate: new Date(distributedFundTxDate), + }) + .where('id = :id', { id: existingRecord.id }) + .execute(); + results.push( + `Updated: Project ${projectId}, Round ${qfRoundId}, Matching Fund: ${matchingFund}`, + ); + } else { + results.push( + `Project QfRoundHistory Not found for Project ${projectId}, Round ${qfRoundId}.`, + ); + } + } + + return { + notice: { + message: `Operations completed:\n${results.join('\n')}`, + type: 'success', + }, + }; + }, + }, updateQfRoundHistories: { actionType: 'resource', isVisible: true, From 4e59a2bb37f5de1d6927e299eca378dd72ed16fd Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 6 Sep 2024 17:13:15 -0500 Subject: [PATCH 3/5] fix eslint --- src/server/adminJs/tabs/qfRoundHistoryTab.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/server/adminJs/tabs/qfRoundHistoryTab.ts b/src/server/adminJs/tabs/qfRoundHistoryTab.ts index 680369db2..ad94760cd 100644 --- a/src/server/adminJs/tabs/qfRoundHistoryTab.ts +++ b/src/server/adminJs/tabs/qfRoundHistoryTab.ts @@ -1,9 +1,8 @@ +import adminJs from 'adminjs'; import { canAccessQfRoundHistoryAction, ResourceActions, } from '../adminJsPermissions'; -import adminJs from 'adminjs'; - import { QfRoundHistory } from '../../../entities/qfRoundHistory'; import { AdminJsContextInterface, @@ -63,7 +62,7 @@ export const qfRoundHistoryTab = { getValue: record => { return record.params.project?.id || record.params.projectId; }, - renderValue: (value, record) => { + renderValue: (value, _record) => { return value ? `Project ${value}` : 'N/A'; }, }, @@ -89,7 +88,7 @@ export const qfRoundHistoryTab = { getValue: record => { return record.params.qfRound?.id || record.params.qfRoundId; }, - renderValue: (value, record) => { + renderValue: (value, _record) => { return value ? `QF Round ${value}` : 'N/A'; }, }, @@ -176,7 +175,7 @@ export const qfRoundHistoryTab = { component: adminJs.bundle( './components/CustomQfRoundMultiUpdateComponent', ), - handler: async (request, response, context) => { + handler: async (request, _response, _context) => { const { records } = request.payload; const results: string[] = []; @@ -192,7 +191,7 @@ export const qfRoundHistoryTab = { distributedFundTxDate, } = record; - let existingRecord = await QfRoundHistory.findOne({ + const existingRecord = await QfRoundHistory.findOne({ where: { projectId, qfRoundId }, }); From 78e19118a2be8bebf92bd42f86b42525dd072f09 Mon Sep 17 00:00:00 2001 From: Carlos Date: Sun, 8 Sep 2024 00:31:46 -0500 Subject: [PATCH 4/5] fix minor form issues --- .../CustomQfRoundMultiUpdateComponent.tsx | 30 +++++++++++-------- src/server/adminJs/tabs/qfRoundHistoryTab.ts | 3 +- src/server/bootstrap.ts | 2 +- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/server/adminJs/tabs/components/CustomQfRoundMultiUpdateComponent.tsx b/src/server/adminJs/tabs/components/CustomQfRoundMultiUpdateComponent.tsx index 02bc3109c..644d6ad82 100644 --- a/src/server/adminJs/tabs/components/CustomQfRoundMultiUpdateComponent.tsx +++ b/src/server/adminJs/tabs/components/CustomQfRoundMultiUpdateComponent.tsx @@ -17,8 +17,18 @@ const RecordInput = ({ index, record, updateRecord, removeRecord }) => ( updateRecord(index, 'qfroundId', e.target.value)} + value={record.qfRoundId} + onChange={e => updateRecord(index, 'qfRoundId', e.target.value)} + required + /> + + + + + updateRecord(index, 'matchingFund', e.target.value) + } required /> @@ -44,17 +54,11 @@ const RecordInput = ({ index, record, updateRecord, removeRecord }) => ( -