diff --git a/package-lock.json b/package-lock.json index d4e757c0..a9c51776 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "chia-web2-gateway": "^1.0.10", "components": "^0.1.0", "dayjs": "^1.11.10", + "diff": "^5.2.0", "express": "^4.19.2", "flowbite": "^2.3.0", "flowbite-react": "^0.7.8", @@ -30,8 +31,10 @@ "qrcode.react": "^3.1.0", "react": "^18.2.0", "react-content-loader": "^7.0.0", + "react-diff-view": "^3.2.1", "react-dom": "^18.2.0", "react-intl": "^6.6.5", + "react-json-view-compare": "^2.0.2", "react-redux": "^9.1.1", "react-router-dom": "^6.22.3", "react-webview": "^0.1.0", @@ -4448,6 +4451,19 @@ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, "node_modules/dir-compare": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-3.3.0.tgz", @@ -6055,6 +6071,11 @@ "ini": "^1.3.2" } }, + "node_modules/gitdiff-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/gitdiff-parser/-/gitdiff-parser-0.3.1.tgz", + "integrity": "sha512-YQJnY8aew65id8okGxKCksH3efDCJ9HzV7M9rsvd65habf39Pkh4cgYJ27AaoDMqo1X98pgNJhNMrm/kpV7UVQ==" + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -8628,6 +8649,22 @@ "react": ">=16.0.0" } }, + "node_modules/react-diff-view": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/react-diff-view/-/react-diff-view-3.2.1.tgz", + "integrity": "sha512-JoDahgiyeReeH9W9lrI3Z4c4esbd/HNAOdThj6Pce/ZAukFBmXSbZ4Qv8ayo7yow+fTpRNfqtQ9gX5nArEi08w==", + "dependencies": { + "classnames": "^2.3.2", + "diff-match-patch": "^1.0.5", + "gitdiff-parser": "^0.3.1", + "lodash": "^4.17.21", + "shallow-equal": "^3.1.0", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -8684,6 +8721,27 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-json-view-compare": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/react-json-view-compare/-/react-json-view-compare-2.0.2.tgz", + "integrity": "sha512-we+OMLFR2FGqHeoqfubQnMhY5V4jLK15/cOnh7cj+v6v7XpizdJu4oSfbEwRWxvTzywSTqpBw/xL0kPZ2YkLIg==", + "dependencies": { + "react-json-viewer-cool": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-json-view-compare/node_modules/react-json-viewer-cool": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-json-viewer-cool/-/react-json-viewer-cool-2.0.0.tgz", + "integrity": "sha512-2bCC9szMbh9PEK5WmAv2253MwjWdK/58RCleqIqvtVjoFFCtWRPXKbe+uK6cxxGRrtCPFLJE+4H1+T+pUQltLQ==", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0", + "react-dom": "^16.14.0 || ^17.0.0" + } + }, "node_modules/react-redux": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.1.tgz", @@ -9415,6 +9473,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallow-equal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==" + }, "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", @@ -10749,6 +10812,14 @@ "node": ">=12.0.0" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 8c493cc3..dbf43b8a 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "chia-web2-gateway": "^1.0.10", "components": "^0.1.0", "dayjs": "^1.11.10", + "diff": "^5.2.0", "express": "^4.19.2", "flowbite": "^2.3.0", "flowbite-react": "^0.7.8", @@ -45,8 +46,10 @@ "qrcode.react": "^3.1.0", "react": "^18.2.0", "react-content-loader": "^7.0.0", + "react-diff-view": "^3.2.1", "react-dom": "^18.2.0", "react-intl": "^6.6.5", + "react-json-view-compare": "^2.0.2", "react-redux": "^9.1.1", "react-router-dom": "^6.22.3", "react-webview": "^0.1.0", diff --git a/src/renderer/api/cadt/v1/index.ts b/src/renderer/api/cadt/v1/index.ts index b3e4c993..09418620 100644 --- a/src/renderer/api/cadt/v1/index.ts +++ b/src/renderer/api/cadt/v1/index.ts @@ -1,46 +1,49 @@ -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; -import initialState from '@/store/slices/app/app.initialstate'; // Ensure this path is correct - -const projectsTag = 'projects'; -const organizationsTag = 'organizations'; -const unitsTag = 'projects'; -const auditTag = 'audit'; -const issuancesTag = 'issuances'; - -const baseQuery = fetchBaseQuery({ - baseUrl: '/', -}); - -const baseQueryWithDynamicHost = async (args, api, extraOptions) => { - let modifiedArgs = args; - const state = api.getState(); - const currentHost = state.app.apiHost; - - // Check if currentHost is equal to the initialState's apiHost - const effectiveHost = (currentHost === initialState.apiHost && import.meta.env.VITE_API_HOST) ? import.meta.env.VITE_API_HOST : currentHost; - - if (!args.url.startsWith('/')) { - return await baseQuery(args, api, extraOptions); - } - - // Modify the URL based on the effectiveHost - if (typeof args === 'string') { - modifiedArgs = `${effectiveHost}${args}`; - } else if (args && typeof args === 'object') { - modifiedArgs = { - ...args, - url: `${effectiveHost}${args.url}`, - }; - } - - return await baseQuery(modifiedArgs, api, extraOptions); -}; - -export const cadtApi = createApi({ - baseQuery: baseQueryWithDynamicHost, - reducerPath: 'cadtApi', - tagTypes: [projectsTag, organizationsTag, unitsTag, auditTag, issuancesTag], - endpoints: () => ({}), -}); - -export { projectsTag, organizationsTag, unitsTag, auditTag, issuancesTag }; +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import initialState from '@/store/slices/app/app.initialstate'; + +const projectsTag = 'projects'; +const organizationsTag = 'organizations'; +const unitsTag = 'projects'; +const auditTag = 'audit'; +const issuancesTag = 'issuances'; +const stagedProjectsTag = 'stagedProjects'; +const stagedUnitsTag = 'stagedUnits'; + +const baseQuery = fetchBaseQuery({ + baseUrl: '/', +}); + +const baseQueryWithDynamicHost = async (args, api, extraOptions) => { + let modifiedArgs = args; + const state = api.getState(); + const currentHost = state.app.apiHost; + + // Check if currentHost is equal to the initialState's apiHost + const effectiveHost = + currentHost === initialState.apiHost && import.meta.env.VITE_API_HOST ? import.meta.env.VITE_API_HOST : currentHost; + + if (!args.url.startsWith('/')) { + return await baseQuery(args, api, extraOptions); + } + + // Modify the URL based on the effectiveHost + if (typeof args === 'string') { + modifiedArgs = `${effectiveHost}${args}`; + } else if (args && typeof args === 'object') { + modifiedArgs = { + ...args, + url: `${effectiveHost}${args.url}`, + }; + } + + return await baseQuery(modifiedArgs, api, extraOptions); +}; + +export const cadtApi = createApi({ + baseQuery: baseQueryWithDynamicHost, + reducerPath: 'cadtApi', + tagTypes: [projectsTag, organizationsTag, unitsTag, auditTag, stagedProjectsTag, stagedUnitsTag, issuancesTag], + endpoints: () => ({}), +}); + +export { projectsTag, organizationsTag, unitsTag, auditTag, stagedProjectsTag, stagedUnitsTag, issuancesTag }; diff --git a/src/renderer/api/cadt/v1/staging/index.ts b/src/renderer/api/cadt/v1/staging/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/renderer/api/cadt/v1/staging/staging.api.ts b/src/renderer/api/cadt/v1/staging/staging.api.ts new file mode 100644 index 00000000..73dae8dc --- /dev/null +++ b/src/renderer/api/cadt/v1/staging/staging.api.ts @@ -0,0 +1,33 @@ +import { cadtApi, stagedProjectsTag, stagedUnitsTag } from '../'; + +const stagingApi = cadtApi.injectEndpoints({ + endpoints: (builder) => ({ + getStagedProjects: builder.query({ + query: () => { + const params: { type: string } = { type: 'projects' }; + + return { + url: `/v1/staging`, + params, + method: 'GET', + }; + }, + providesTags: [stagedProjectsTag], + }), + + getStagedUnits: builder.query({ + query: () => { + const params: { type: string } = { type: 'units' }; + + return { + url: `/v1/staging`, + params, + method: 'GET', + }; + }, + providesTags: [stagedUnitsTag], + }), + }), +}); + +export const { useGetStagedProjectsQuery, useGetStagedUnitsQuery } = stagingApi; diff --git a/src/renderer/components/blocks/index.ts b/src/renderer/components/blocks/index.ts index 6395aebc..1bdb4296 100644 --- a/src/renderer/components/blocks/index.ts +++ b/src/renderer/components/blocks/index.ts @@ -4,3 +4,4 @@ export * from './modals'; export * from './tables'; export * from './widgets'; export * from './forms'; +export * from './tabs'; diff --git a/src/renderer/components/blocks/modals/StagingDiffModal.tsx b/src/renderer/components/blocks/modals/StagingDiffModal.tsx new file mode 100644 index 00000000..5adf29ad --- /dev/null +++ b/src/renderer/components/blocks/modals/StagingDiffModal.tsx @@ -0,0 +1,35 @@ +import React, { useMemo } from 'react'; +import { DiffViewer, Modal } from '@/components'; +import { FormattedMessage } from 'react-intl'; +import { useGetStagedProjectsQuery } from '@/api/cadt/v1/staging/staging.api'; + +interface ProjectModalProps { + stagingUuid: string; + onClose: () => void; +} + +const StagingDiffModal: React.FC = ({ stagingUuid, onClose }: ProjectModalProps) => { + const { data: stagingData, isLoading } = useGetStagedProjectsQuery(); + + const changeRecord: any = useMemo(() => { + if (isLoading || !stagingData) { + return undefined; + } + return stagingData.find((record) => record.uuid === stagingUuid); + }, [stagingData, isLoading, stagingUuid]); + + if (isLoading || !changeRecord) { + return null; + } + + return ( + + + + + {isLoading ?

loading...

: }
+
+ ); +}; + +export { StagingDiffModal }; diff --git a/src/renderer/components/blocks/modals/index.ts b/src/renderer/components/blocks/modals/index.ts index b3c6f392..6f979de3 100644 --- a/src/renderer/components/blocks/modals/index.ts +++ b/src/renderer/components/blocks/modals/index.ts @@ -1,4 +1,5 @@ -export * from './ProjectModal'; -export * from './CreateOrganizationModal'; -export * from './ConnectModal'; -export * from './UnitModal'; \ No newline at end of file +export * from './ProjectModal'; +export * from './CreateOrganizationModal'; +export * from './ConnectModal'; +export * from './UnitModal'; +export * from './StagingDiffModal'; diff --git a/src/renderer/components/blocks/tables/StagingTable.tsx b/src/renderer/components/blocks/tables/StagingTable.tsx new file mode 100644 index 00000000..a8374900 --- /dev/null +++ b/src/renderer/components/blocks/tables/StagingTable.tsx @@ -0,0 +1,78 @@ +import React, { useMemo } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Badge, DataTable } from '@/components'; +import dayjs from 'dayjs'; + +interface TableProps { + data: any[]; + isLoading: boolean; + setOrder: (sort: string) => void; + onRowClick: (row: any) => void; + order: string; +} + +const StagingTable: React.FC = ({ data, isLoading, onRowClick, setOrder, order }) => { + const columns = useMemo( + () => [ + { + title: , + key: 'table', + render: (row: any) => {row?.table || '-'}, + }, + { + title: , + key: 'action', + render: (row: any) => { + if (row.action === 'INSERT') { + return {row.action}; + } + if (row.action === 'UPDATE') { + return {row.action}; + } + if (row.action === 'DELETE') { + return {row.action}; + } + return {'--'}; + }, + }, + { + title: , + key: 'uuid', + render: (row: any) =>
{row.uuid}
, + }, + { + title: , + key: 'createdAt', + render: (row: any) => { + return <>{dayjs(new Date(row.createdAt)).format('MMMM D, YYYY h:mm:ss A')}; + }, + }, + { + title: , + key: 'updatedAt', + render: (row: any) => { + return <>{dayjs(new Date(row.updatedAt)).format('MMMM D, YYYY h:mm:ss A')}; + }, + }, + ], + [], + ); + + return ( + <> +
+ +
+ + ); +}; + +export { StagingTable }; diff --git a/src/renderer/components/blocks/tables/index.ts b/src/renderer/components/blocks/tables/index.ts index 16a7fe5f..d0d5504b 100644 --- a/src/renderer/components/blocks/tables/index.ts +++ b/src/renderer/components/blocks/tables/index.ts @@ -1,6 +1,8 @@ -export * from './ProjectsListTable'; -export * from './SkeletonTable'; -export * from './UnitsListTable'; -export * from './AuditsTable'; -export * from './GlossaryTable'; -export * from './CompactUnitsTable'; \ No newline at end of file +export * from './ProjectsListTable'; +export * from './SkeletonTable'; +export * from './UnitsListTable'; +export * from './AuditsTable'; +export * from './GlossaryTable'; +export * from './StagingTable'; +export * from './OrganizationSubscriptionsTable'; +export * from './CompactUnitsTable'; diff --git a/src/renderer/components/blocks/tabs/MyCommittedProjectsTab.tsx b/src/renderer/components/blocks/tabs/CommittedProjectsTab.tsx similarity index 93% rename from src/renderer/components/blocks/tabs/MyCommittedProjectsTab.tsx rename to src/renderer/components/blocks/tabs/CommittedProjectsTab.tsx index 84da9716..5149ccb1 100644 --- a/src/renderer/components/blocks/tabs/MyCommittedProjectsTab.tsx +++ b/src/renderer/components/blocks/tabs/CommittedProjectsTab.tsx @@ -11,7 +11,7 @@ interface PageTabProps { setIsLoading: (isLoading: boolean) => void; } -const MyCommittedProjectsTab: React.FC = ({ orgUid, search, setIsLoading }: PageTabProps) => { +const CommittedProjectsTab: React.FC = ({ orgUid, search, setIsLoading }: PageTabProps) => { const [currentPage, setCurrentPage] = useQueryParamState('page', '1'); const [order, setOrder] = useQueryParamState('order', undefined); const handleSetOrder = useColumnOrderHandler(order, setOrder); @@ -69,4 +69,4 @@ const MyCommittedProjectsTab: React.FC = ({ orgUid, search, setIsL ); }; -export { MyCommittedProjectsTab }; +export { CommittedProjectsTab }; diff --git a/src/renderer/components/blocks/tabs/MyCommittedUnitsTab.tsx b/src/renderer/components/blocks/tabs/CommittedUnitsTab.tsx similarity index 93% rename from src/renderer/components/blocks/tabs/MyCommittedUnitsTab.tsx rename to src/renderer/components/blocks/tabs/CommittedUnitsTab.tsx index 3bc91163..e48b8102 100644 --- a/src/renderer/components/blocks/tabs/MyCommittedUnitsTab.tsx +++ b/src/renderer/components/blocks/tabs/CommittedUnitsTab.tsx @@ -11,7 +11,7 @@ interface PageTabProps { setIsLoading: (isLoading: boolean) => void; } -const MyCommittedUnitsTab: React.FC = ({ orgUid, search, setIsLoading }: PageTabProps) => { +const CommittedUnitsTab: React.FC = ({ orgUid, search, setIsLoading }: PageTabProps) => { const [currentPage, setCurrentPage] = useQueryParamState('page', '1'); const [order, setOrder] = useQueryParamState('order', undefined); const handleSetOrder = useColumnOrderHandler(order, setOrder); @@ -65,4 +65,4 @@ const MyCommittedUnitsTab: React.FC = ({ orgUid, search, setIsLoad ); }; -export { MyCommittedUnitsTab }; +export { CommittedUnitsTab }; diff --git a/src/renderer/components/blocks/tabs/StagingTableTab.tsx b/src/renderer/components/blocks/tabs/StagingTableTab.tsx new file mode 100644 index 00000000..1c803eb4 --- /dev/null +++ b/src/renderer/components/blocks/tabs/StagingTableTab.tsx @@ -0,0 +1,47 @@ +import { FormattedMessage } from 'react-intl'; +import { SkeletonTable, StagingDiffModal, StagingTable } from '@/components'; +import React from 'react'; +import { useColumnOrderHandler, useQueryParamState, useWildCardUrlHash } from '@/hooks'; + +interface PageTabProps { + stagingData: any[]; + showLoading: boolean; +} + +const StagingTableTab: React.FC = ({ stagingData, showLoading }: PageTabProps) => { + const [order, setOrder] = useQueryParamState('order', undefined); + const handleSetOrder = useColumnOrderHandler(order, setOrder); + const [stagingDiffFragment, stagingDiffModalActive, setStagingDiffModalActive] = useWildCardUrlHash('staging'); + + if (showLoading) { + return ; + } + + if (!stagingData) { + return ; + } + + return ( + <> + {showLoading ? ( + + ) : ( + setStagingDiffModalActive(true, row.uuid)} + /> + )} + {stagingDiffModalActive && ( + setStagingDiffModalActive(false)} + stagingUuid={stagingDiffFragment.replace('staging-', '')} + /> + )} + + ); +}; + +export { StagingTableTab }; diff --git a/src/renderer/components/blocks/tabs/index.ts b/src/renderer/components/blocks/tabs/index.ts index 61983471..5c0d6779 100644 --- a/src/renderer/components/blocks/tabs/index.ts +++ b/src/renderer/components/blocks/tabs/index.ts @@ -1 +1,3 @@ -export * from './MyCommittedProjectsTab'; +export * from './CommittedProjectsTab'; +export * from './CommittedUnitsTab'; +export * from './StagingTableTab'; diff --git a/src/renderer/components/blocks/widgets/DiffViewer.tsx b/src/renderer/components/blocks/widgets/DiffViewer.tsx new file mode 100644 index 00000000..f3eedb8d --- /dev/null +++ b/src/renderer/components/blocks/widgets/DiffViewer.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import JSONDiffReact from 'react-json-view-compare'; + +interface ChangeRecord { + id: number; + uuid: string; + table: string; + action: string; + commited: boolean; + failedCommit: boolean; + isTransfer: boolean; + createdAt: string; + updatedAt: string; + diff: { + original: any; + change: any[]; + }; +} + +interface DiffViewerProps { + data: ChangeRecord; +} + +const DiffViewer: React.FC = ({ data }) => { + return ( +
+

+ Change for {data.table} - Action: {data.action} +

+ 1 ? data.diff.change : data.diff.change[0]} + collapsed={false} + compareArraysInOrder={true} + leftTitle="Original" + rightTitle="Modified" + /> +
+ ); +}; + +export { DiffViewer }; diff --git a/src/renderer/components/blocks/widgets/OrganizationSubscriptionsManager.tsx b/src/renderer/components/blocks/widgets/OrganizationSubscriptionsManager.tsx index c9d683ed..b26afafc 100644 --- a/src/renderer/components/blocks/widgets/OrganizationSubscriptionsManager.tsx +++ b/src/renderer/components/blocks/widgets/OrganizationSubscriptionsManager.tsx @@ -1,8 +1,12 @@ import React from 'react'; -import { ComponentCenteredSpinner, IndeterminateProgressOverlay, SkeletonTable } from '@/components'; +import { + ComponentCenteredSpinner, + IndeterminateProgressOverlay, + OrganizationSubscriptionsTable, + SkeletonTable, +} from '@/components'; import { FormattedMessage } from 'react-intl'; import { useGetDefaultOrgListQuery } from '@/api/cadt/v1/governance'; -import { OrganizationSubscriptionsTable } from '@/components/blocks/tables/OrganizationSubscriptionsTable'; const OrganizationSubscriptionsManager: React.FC = () => { const { diff --git a/src/renderer/components/blocks/widgets/index.ts b/src/renderer/components/blocks/widgets/index.ts index 39745394..e73dac95 100644 --- a/src/renderer/components/blocks/widgets/index.ts +++ b/src/renderer/components/blocks/widgets/index.ts @@ -3,4 +3,5 @@ export * from './OrganizationSelector'; export * from './SyncIndicator'; export * from './OrgUidBadge'; export * from './ThemeSelector'; -export * from './UnitSummary'; \ No newline at end of file +export * from './UnitSummary'; +export * from './DiffViewer'; diff --git a/src/renderer/components/proxy/Badge.tsx b/src/renderer/components/proxy/Badge.tsx new file mode 100644 index 00000000..fd9edaeb --- /dev/null +++ b/src/renderer/components/proxy/Badge.tsx @@ -0,0 +1,7 @@ +import { Badge as FlowbiteBadge, BadgeProps } from 'flowbite-react'; + +function Badge({ children, ...props }: BadgeProps) { + return {children}; +} + +export { Badge }; diff --git a/src/renderer/components/proxy/index.ts b/src/renderer/components/proxy/index.ts index bf1b79f7..764d5575 100644 --- a/src/renderer/components/proxy/index.ts +++ b/src/renderer/components/proxy/index.ts @@ -1,16 +1,17 @@ -export * from './Button'; -export * from './Pagination'; -export * from './Dropdown'; -export * from './TextInput'; -export * from './Spinner'; -export * from './Sidebar'; -export * from './Tooltip'; -export * from './Modal'; -export * from './Tabs'; -export * from './Card'; -export * from './FloatingLabel'; -export * from './Toast'; -export * from './HelperText'; -export * from './Label'; -export * from './FloatingLabel'; -export * from './Select'; \ No newline at end of file +export * from './Button'; +export * from './Pagination'; +export * from './Dropdown'; +export * from './TextInput'; +export * from './Spinner'; +export * from './Sidebar'; +export * from './Tooltip'; +export * from './Modal'; +export * from './Tabs'; +export * from './Card'; +export * from './FloatingLabel'; +export * from './Toast'; +export * from './HelperText'; +export * from './Label'; +export * from './FloatingLabel'; +export * from './Select'; +export * from './Badge'; diff --git a/src/renderer/pages/MyProjectsPage.tsx b/src/renderer/pages/MyProjectsPage.tsx index 3d0c74c0..37b2af3a 100644 --- a/src/renderer/pages/MyProjectsPage.tsx +++ b/src/renderer/pages/MyProjectsPage.tsx @@ -4,10 +4,12 @@ import { useQueryParamState } from '@/hooks'; import { debounce } from 'lodash'; import { Button, + CommittedProjectsTab, ComponentCenteredSpinner, IndeterminateProgressOverlay, OrgUidBadge, SearchBox, + StagingTableTab, SyncIndicator, Tabs, } from '@/components'; @@ -15,7 +17,7 @@ import { FormattedMessage } from 'react-intl'; import { useGetOrganizationsMapQuery } from '@/api/cadt/v1/organizations'; import { Organization } from '@/schemas/Organization.schema'; import { useNavigate } from 'react-router-dom'; -import { MyCommittedProjectsTab } from '@/components/blocks/tabs/MyCommittedProjectsTab'; +import { useGetStagedProjectsQuery } from '@/api/cadt/v1/staging/staging.api'; enum TabTypes { COMMITTED, @@ -25,6 +27,12 @@ enum TabTypes { TRANSFERS, } +interface ProcessedStagingData { + staged: any[]; + pending: any[]; + failed: any[]; +} + const MyProjectsPage: React.FC = () => { const navigate = useNavigate(); const [orgUid, setOrgUid] = useQueryParamState('orgUid', undefined); @@ -34,7 +42,7 @@ const MyProjectsPage: React.FC = () => { const { data: organizationsMap } = useGetOrganizationsMapQuery(null, { skip: !orgUid, }); - + const { data: unprocessedStagedProjects, isLoading: stagingDataLoading } = useGetStagedProjectsQuery(); const { data: organizationsListData, isLoading: organizationsListLoading } = useGetOrganizationsListQuery(); const myOrganization = useMemo( @@ -42,9 +50,27 @@ const MyProjectsPage: React.FC = () => { [organizationsListData], ); + const processedStagingData: ProcessedStagingData = useMemo(() => { + const data: ProcessedStagingData = { staged: [], pending: [], failed: [] }; + if (unprocessedStagedProjects?.forEach) { + unprocessedStagedProjects.forEach((stagedProject: any) => { + if (unprocessedStagedProjects?.forEach) { + if (!stagedProject.commited && !stagedProject.failedCommit && !stagedProject.isTransfer) { + data.staged.push(stagedProject); + } else if (stagedProject.commited && !stagedProject.failedCommit && !stagedProject.isTransfer) { + data.pending.push(stagedProject); + } else if (!stagedProject.commited && stagedProject.failedCommit && !stagedProject.isTransfer) { + data.failed.push(stagedProject); + } + } + }); + } + return data; + }, [unprocessedStagedProjects]); + const contentsLoading = useMemo(() => { - return committedDataLoading; - }, [committedDataLoading]); + return committedDataLoading || stagingDataLoading; + }, [committedDataLoading, stagingDataLoading]); useEffect(() => { if (myOrganization) { @@ -83,11 +109,38 @@ const MyProjectsPage: React.FC = () => { setActiveTab(tab)}> }> - + + + + + {' (' + String(processedStagingData.staged.length + ') ')} +

+ } + > + +
+ + + {' (' + String(processedStagingData.pending.length + ') ')} +

+ } + > + +
+ + + {' (' + String(processedStagingData.failed.length + ') ')} +

+ } + > +
- }>todo staging - }>todo pending - }>todo failed }>todo transfers
diff --git a/src/renderer/pages/MyUnitsPage.tsx b/src/renderer/pages/MyUnitsPage.tsx index 78d14518..18687da6 100644 --- a/src/renderer/pages/MyUnitsPage.tsx +++ b/src/renderer/pages/MyUnitsPage.tsx @@ -1,20 +1,23 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { useGetOrganizationsListQuery, useGetOrganizationsMapQuery } from '@/api'; +import { useGetOrganizationsListQuery } from '@/api'; import { useQueryParamState } from '@/hooks'; -import { debounce, DebouncedFunc } from 'lodash'; +import { debounce } from 'lodash'; import { Button, + CommittedUnitsTab, ComponentCenteredSpinner, IndeterminateProgressOverlay, OrgUidBadge, SearchBox, + StagingTableTab, SyncIndicator, Tabs, } from '@/components'; import { FormattedMessage } from 'react-intl'; -import { useNavigate } from 'react-router-dom'; +import { useGetOrganizationsMapQuery } from '@/api/cadt/v1/organizations'; import { Organization } from '@/schemas/Organization.schema'; -import { MyCommittedUnitsTab } from '@/components/blocks/tabs/MyCommittedUnitsTab'; +import { useNavigate } from 'react-router-dom'; +import { useGetStagedUnitsQuery } from '@/api/cadt/v1/staging/staging.api'; enum TabTypes { COMMITTED, @@ -24,6 +27,12 @@ enum TabTypes { TRANSFERS, } +interface ProcessedStagingData { + staged: any[]; + pending: any[]; + failed: any[]; +} + const MyUnitsPage: React.FC = () => { const navigate = useNavigate(); const [orgUid, setOrgUid] = useQueryParamState('orgUid', undefined); @@ -33,7 +42,7 @@ const MyUnitsPage: React.FC = () => { const { data: organizationsMap } = useGetOrganizationsMapQuery(null, { skip: !orgUid, }); - + const { data: unprocessedStagedUnits, isLoading: stagingDataLoading } = useGetStagedUnitsQuery(); const { data: organizationsListData, isLoading: organizationsListLoading } = useGetOrganizationsListQuery(); const myOrganization = useMemo( @@ -41,9 +50,27 @@ const MyUnitsPage: React.FC = () => { [organizationsListData], ); + const processedStagingData: ProcessedStagingData = useMemo(() => { + const data: ProcessedStagingData = { staged: [], pending: [], failed: [] }; + if (unprocessedStagedUnits?.forEach) { + unprocessedStagedUnits.forEach((stagedUnit: any) => { + if (stagedUnit?.table === 'Units') { + if (!stagedUnit.commited && !stagedUnit.failedCommit && !stagedUnit.isTransfer) { + data.staged.push(stagedUnit); + } else if (stagedUnit.commited && !stagedUnit.failedCommit && !stagedUnit.isTransfer) { + data.pending.push(stagedUnit); + } else if (!stagedUnit.commited && stagedUnit.failedCommit && !stagedUnit.isTransfer) { + data.failed.push(stagedUnit); + } + } + }); + } + return data; + }, [unprocessedStagedUnits]); + const contentsLoading = useMemo(() => { - return committedDataLoading; - }, [committedDataLoading]); + return committedDataLoading || stagingDataLoading; + }, [committedDataLoading, stagingDataLoading]); useEffect(() => { if (myOrganization) { @@ -57,7 +84,7 @@ const MyUnitsPage: React.FC = () => { } }, [myOrganization, navigate, organizationsListLoading]); - const handleSearchChange: DebouncedFunc = useCallback( + const handleSearchChange = useCallback( debounce((event: any) => { setSearch(event.target.value); }, 800), @@ -72,7 +99,7 @@ const MyUnitsPage: React.FC = () => {
{contentsLoading && }
- {activeTab === TabTypes.COMMITTED && } @@ -82,11 +109,38 @@ const MyUnitsPage: React.FC = () => { setActiveTab(tab)}> }> - + + + + + {' (' + String(processedStagingData.staged.length + ') ')} +

+ } + > + +
+ + + {' (' + String(processedStagingData.pending.length + ') ')} +

+ } + > + +
+ + + {' (' + String(processedStagingData.failed.length + ') ')} +

+ } + > +
- }>todo staging - }>todo pending - }>todo failed }>todo transfers
diff --git a/src/renderer/translations/tokens/en-US.json b/src/renderer/translations/tokens/en-US.json index 86e2c837..3a443889 100644 --- a/src/renderer/translations/tokens/en-US.json +++ b/src/renderer/translations/tokens/en-US.json @@ -92,6 +92,11 @@ "transfers": "Transfers", "my-units": "My Units", "create-unit": "Create Unit", + "action": "Action", + "date-created": "Date Created", + "date-last-updated": "Date Last Updated", + "uuid": "UUID", + "staging-diff": "Staging Diff", "retry": "Retry", "unit": "Unit", "detailed-unit-view": "Detailed Unit View"