diff --git a/README.md b/README.md index 578e3dc..901bbe3 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ define RTKQuery endpoints in the /v1 directory. Note that when exporting a hook is prepended with use, and appended with either query or mutation. To export resource query hook foo, the export name will need to be useFooQuery. For mutation hook foo it will need to be useFooMutation. -sampleProjects.api.ts can be deleted as it is only use in other sample code that will be deleted, but it is highly +projects.api.ts can be deleted as it is only use in other sample code that will be deleted, but it is highly recommended that system.api.ts be kept and the resource url's changed to the /health equivalent for the backend the app will be connected to. the application uses these system hooks to determine if the backend is available or not. removing it will require refactoring, and it will likely end-up being reimplemented over the course of development. @@ -75,8 +75,8 @@ The pattern for displaying data has 3 general steps: of how to display the data - the structure, data, and options are passed to a common base component responsible for rendering the data -This pattern is exemplified by pages/ListPageSample.tsx, components/blocks/tables/ProjectsListTable.tsx, and -components/layout/Datatable.tsx. ListPageSample.tsx fetches the data via the RTKQuery hooks, then passes the data, +This pattern is exemplified by pages/TokensPage.tsx, components/blocks/tables/ProjectsListTable.tsx, and +components/layout/Datatable.tsx. TokensPage.tsx fetches the data via the RTKQuery hooks, then passes the data, filters, and sorts to ProjectsListTable.tsx, which defines the structure (columns) of the table and passes the structure and data down to the generalized DataTable.tsx. The project table and data table are directly controlled at the page level and make no assumptions about the data or how to display it. The data table can be used in any capacity for any table and @@ -86,5 +86,5 @@ For all other operations that don't involve fetching and sorting data, a pattern pull date directly from the api hooks and are parent component-agnostic is used. Prop drilling is explicitly avoided. For deep-linking components and state, the hooks/ directory contains hooks for manipulating query params and url hashes. -these hooks operate much like the useState hook with additional arguments. pages/ListPageSample.tsx has examples for +these hooks operate much like the useState hook with additional arguments. pages/TokensPage.tsx has examples for how to use these hooks. \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index a9067d9..e423100 100644 --- a/src/main.ts +++ b/src/main.ts @@ -32,7 +32,7 @@ function createWindow() { // Load URL based on the environment const loadUrl = process.env.NODE_ENV === 'development' - ? 'http://localhost:5173/' // Development URL + ? 'http://localhost:5174/' // Development URL : `http://localhost:${serverPort}/`; // Production URL served by Express win.loadURL(loadUrl); diff --git a/src/renderer/api/cadt/v1/index.ts b/src/renderer/api/cadt/v1/index.ts index 9c60d8b..f22b453 100644 --- a/src/renderer/api/cadt/v1/index.ts +++ b/src/renderer/api/cadt/v1/index.ts @@ -1,10 +1,6 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; import initialState from '@/store/slices/app/app.initialstate'; -const projectsTag = 'projects'; - -const sampleTag = 'removeThisExample'; - const baseQuery = fetchBaseQuery({ baseUrl: '/', }); @@ -43,8 +39,5 @@ const baseQueryWithDynamicHost = async (args, api, extraOptions) => { export const cadtApi = createApi({ baseQuery: baseQueryWithDynamicHost, reducerPath: 'cadtApi', - tagTypes: [projectsTag, sampleTag], endpoints: () => ({}), }); - -export { projectsTag, sampleTag }; diff --git a/src/renderer/api/cadt/v1/projects.api.ts b/src/renderer/api/cadt/v1/projects.api.ts new file mode 100644 index 0000000..2bc2e24 --- /dev/null +++ b/src/renderer/api/cadt/v1/projects.api.ts @@ -0,0 +1,99 @@ +import { cadtApi } from './index'; +import { Project } from '@/schemas/Project.schema'; + +interface GetProjectsParams { + page?: number; + orgUid?: string | null; + search?: string | null; + order?: string | null; +} + +interface GetProjectsById { + projectIds: string[]; +} + +interface GetProjectParams { + warehouseProjectId: string; +} + +interface GetProjectsResponse { + page: number; + pageCount: number; + data: Project[]; +} + +const projectsApi = cadtApi.injectEndpoints({ + endpoints: (builder) => ({ + getProjects: builder.query({ + query: ({ page, orgUid, search, order }: GetProjectsParams) => { + // Initialize the params object with page and limit + const params: GetProjectsParams & { limit: number } = { page, limit: 10 }; + + if (orgUid) { + params.orgUid = orgUid; + } + + if (search) { + params.search = search.replace(/[^a-zA-Z0-9 _.-]+/, ''); + } + + if (order) { + params.order = order; + } + + return { + url: `/projects`, + params, + method: 'GET', + }; + }, + }), + + getProjectsImmediate: builder.mutation({ + query: ({ orgUid, search, order }: GetProjectsParams) => { + // Initialize the params object with page and limit + const params: GetProjectsParams = {}; + + if (orgUid) { + params.orgUid = orgUid; + } + + if (search) { + params.search = search.replace(/[^a-zA-Z0-9 _.-]+/, ''); + } + + if (order) { + params.order = order; + } + + return { + url: `/projects`, + params, + method: 'GET', + }; + }, + }), + + getProject: builder.query({ + query: ({ warehouseProjectId }: GetProjectParams) => ({ + url: `/projects`, + params: { warehouseProjectId }, + method: 'GET', + }), + }), + + getProjectsByIdsImmediate: builder.mutation({ + query: ({ projectIds }: GetProjectsById) => { + const queryParams = new URLSearchParams(); + projectIds.forEach((projectId) => queryParams.append('projectIds', projectId)); + return { + url: `/projects?${queryParams}`, + method: 'GET', + }; + }, + }), + }), +}); + +export const { useGetProjectsQuery, useGetProjectsImmediateMutation, useGetProjectsByIdsImmediateMutation } = + projectsApi; diff --git a/src/renderer/api/cadt/v1/sampleProjects.api.ts b/src/renderer/api/cadt/v1/sampleProjects.api.ts deleted file mode 100644 index 6f8e001..0000000 --- a/src/renderer/api/cadt/v1/sampleProjects.api.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { isEmpty, isNil, omit } from 'lodash'; -import { cadtApi, projectsTag } from './index'; -import { Project } from '@/schemas/Project.schema'; - -interface GetProjectsParams { - page?: number; - orgUid?: string | null; - search?: string | null; - order?: string | null; -} - -interface GetProjectParams { - warehouseProjectId: string; -} - -interface DeleteProjectParams { - warehouseProjectId: string; -} - -interface GetProjectsResponse { - page: number; - pageCount: number; - data: Project[]; -} - -/** sample api resource. repurpose or remove */ - -const sampleProjectsApi = cadtApi.injectEndpoints({ - endpoints: (builder) => ({ - getProjects: builder.query({ - query: ({ page, orgUid, search, order }: GetProjectsParams) => { - // Initialize the params object with page and limit - const params: GetProjectsParams & { limit: number } = { page, limit: 10 }; - - if (orgUid) { - params.orgUid = orgUid; - } - - if (search) { - params.search = search.replace(/[^a-zA-Z0-9 _.-]+/, ''); - } - - if (order) { - params.order = order; - } - - return { - url: `/v1/projects`, - params, - method: 'GET', - }; - }, - }), - - getProjectsImmediate: builder.mutation({ - query: ({ orgUid, search, order }: GetProjectsParams) => { - // Initialize the params object with page and limit - const params: GetProjectsParams = {}; - - if (orgUid) { - params.orgUid = orgUid; - } - - if (search) { - params.search = search.replace(/[^a-zA-Z0-9 _.-]+/, ''); - } - - if (order) { - params.order = order; - } - - return { - url: `/v1/projects`, - params, - method: 'GET', - }; - }, - }), - - getProject: builder.query({ - query: ({ warehouseProjectId }: GetProjectParams) => ({ - url: `/v1/projects`, - params: { warehouseProjectId }, - method: 'GET', - }), - providesTags: (_response, _error, { warehouseProjectId }) => [{ type: projectsTag, id: warehouseProjectId }], - }), - - deleteProject: builder.mutation({ - query: ({ warehouseProjectId }: GetProjectParams) => ({ - url: `/v1/projects`, - method: 'DELETE', - headers: { 'Content-Type': 'application/json' }, - body: { warehouseProjectId }, - }), - invalidatesTags: (_response, _error, { warehouseProjectId }) => [{ type: projectsTag, id: warehouseProjectId }], - }), - - transferProject: builder.mutation({ - query: (project) => { - if (isNil(project.projectLocations) || isEmpty(project.projectLocations)) { - delete project.projectLocations; - } - - if (isNil(project.labels) || isEmpty(project.labels)) { - delete project.labels; - } - - if (isNil(project.relatedProjects) || isEmpty(project.relatedProjects)) { - delete project.relatedProjects; - } - - if (isNil(project.projectRatings) || isEmpty(project.projectRatings)) { - delete project.projectRatings; - } - - if (isNil(project.estimations) || isEmpty(project.estimations)) { - delete project.estimations; - } - - if (isNil(project.issuances) || isEmpty(project.issuances)) { - delete project.issuances; - } - - if (isNil(project.coBenefits) || isEmpty(project.coBenefits)) { - delete project.coBenefits; - } - - return { - url: `/v1/projects/transfer`, - method: 'PUT', - body: omit(project, ['orgUid', 'timeStaged', 'createdBy', 'updatedBy']), - }; - }, - }), - }), -}); - -export const { - useGetProjectsQuery, - useGetProjectsImmediateMutation, - useGetProjectQuery, - useDeleteProjectMutation, - useTransferProjectMutation, -} = sampleProjectsApi; diff --git a/src/renderer/api/cadt/v1/system.api.ts b/src/renderer/api/cadt/v1/system.api.ts index 69da9b2..086f1ae 100644 --- a/src/renderer/api/cadt/v1/system.api.ts +++ b/src/renderer/api/cadt/v1/system.api.ts @@ -26,7 +26,7 @@ const systemApi = cadtApi.injectEndpoints({ endpoints: (builder) => ({ getHealth: builder.query({ query: ({ apiHost = '', apiKey }) => ({ - url: `${apiHost}/health`, + url: `${apiHost}/healthz`, method: 'GET', headers: apiKey ? { 'X-Api-Key': apiKey } : {}, }), @@ -38,7 +38,7 @@ const systemApi = cadtApi.injectEndpoints({ }), getHealthImmediate: builder.mutation({ query: ({ apiHost = '', apiKey }) => ({ - url: `${apiHost}/health`, + url: `${apiHost}/healthz`, method: 'GET', headers: apiKey ? { 'X-Api-Key': apiKey } : {}, }), diff --git a/src/renderer/api/cadt/v1/units.api.ts b/src/renderer/api/cadt/v1/units.api.ts new file mode 100644 index 0000000..888fa0d --- /dev/null +++ b/src/renderer/api/cadt/v1/units.api.ts @@ -0,0 +1,84 @@ +import { cadtApi } from './index'; +import { Unit } from '@/schemas/Unit.schema'; + +interface GetUnitsParams { + page: number; + orgUid?: string; + search?: string; + order?: string; + filter?: string; +} + +interface GetUnitParams { + warehouseUnitId: string; +} + +export interface GetUnitsResponse { + page: number; + pageCount: number; + data: Unit[]; +} + +const unitsApi = cadtApi.injectEndpoints({ + endpoints: (builder) => ({ + getUntokenizedUnits: builder.query({ + query: ({ page, search, order, filter }: GetUnitsParams) => { + // Initialize the params object with page and limit + const params: GetUnitsParams & { limit: number } = { page, limit: 10 }; + + if (search) { + params.search = search.replace(/[^a-zA-Z0-9 _.-]+/, ''); + } + + if (order) { + params.order = order; + } + + if (filter) { + params.filter = filter; + } + + return { + url: `/units/untokenized`, + params, + method: 'GET', + }; + }, + }), + + getTokenizedUnits: builder.query({ + query: ({ page, search, order, filter }: GetUnitsParams) => { + // Initialize the params object with page and limit + const params: GetUnitsParams & { limit: number } = { page, limit: 10 }; + + if (search) { + params.search = search.replace(/[^a-zA-Z0-9 _.-]+/, ''); + } + + if (order) { + params.order = order; + } + + if (filter) { + params.filter = filter; + } + + return { + url: `/units/tokenized`, + params, + method: 'GET', + }; + }, + }), + + getUnit: builder.query({ + query: ({ warehouseUnitId }: GetUnitParams) => ({ + url: `/v1/units`, + params: { warehouseUnitId }, + method: 'GET', + }), + }), + }), +}); + +export const { useGetTokenizedUnitsQuery, useGetUntokenizedUnitsQuery, useGetUnitQuery } = unitsApi; diff --git a/src/renderer/api/index.ts b/src/renderer/api/index.ts index cc6852f..5b0f7d5 100644 --- a/src/renderer/api/index.ts +++ b/src/renderer/api/index.ts @@ -1,2 +1,2 @@ -export * from './cadt/v1/sampleProjects.api'; +export * from './cadt/v1/projects.api'; export * from './cadt/v1/system.api'; diff --git a/src/renderer/components/blocks/layout/LeftNav.tsx b/src/renderer/components/blocks/layout/LeftNav.tsx index 2133117..fe76519 100644 --- a/src/renderer/components/blocks/layout/LeftNav.tsx +++ b/src/renderer/components/blocks/layout/LeftNav.tsx @@ -26,8 +26,8 @@ const LeftNav = () => { navigate(ROUTES.PROJECTS_LIST)} + active={isActive(ROUTES.CREATE_TOKENS)} + onClick={() => navigate(ROUTES.CREATE_TOKENS)} > diff --git a/src/renderer/components/blocks/modals/CreateTokenModal.tsx b/src/renderer/components/blocks/modals/CreateTokenModal.tsx new file mode 100644 index 0000000..014352f --- /dev/null +++ b/src/renderer/components/blocks/modals/CreateTokenModal.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Button, Modal } from '@/components'; +import { FormattedMessage } from 'react-intl'; + +interface UpsertModalProps { + urlFragmentDerivedData: string; + onClose: () => void; +} + +const CreateTokenModal: React.FC = ({ onClose, urlFragmentDerivedData }: UpsertModalProps) => { + return ( + + + + + +

sample modal

+

{urlFragmentDerivedData}

+
+ + + +
+ ); +}; + +export { CreateTokenModal }; diff --git a/src/renderer/components/blocks/modals/SampleDeepLinkedModal.tsx b/src/renderer/components/blocks/modals/SampleDeepLinkedModal.tsx deleted file mode 100644 index 3095214..0000000 --- a/src/renderer/components/blocks/modals/SampleDeepLinkedModal.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import { Modal } from '@/components'; - -interface UpsertModalProps { - urlFragmentDerivedData: string; - onClose: () => void; -} - -const SampleDeepLinkedModal: React.FC = ({ onClose, urlFragmentDerivedData }: UpsertModalProps) => { - return ( - - sample modal - -

sample modal

-

{urlFragmentDerivedData}

-
-
- ); -}; - -export { SampleDeepLinkedModal }; diff --git a/src/renderer/components/blocks/modals/index.ts b/src/renderer/components/blocks/modals/index.ts index 44c73ec..2c602d7 100644 --- a/src/renderer/components/blocks/modals/index.ts +++ b/src/renderer/components/blocks/modals/index.ts @@ -1,2 +1,2 @@ export * from './ConnectModal'; -export * from './SampleDeepLinkedModal'; +export * from './CreateTokenModal'; diff --git a/src/renderer/components/blocks/tables/ProjectsListTable.tsx b/src/renderer/components/blocks/tables/UntokenizedUnitListTable.tsx similarity index 86% rename from src/renderer/components/blocks/tables/ProjectsListTable.tsx rename to src/renderer/components/blocks/tables/UntokenizedUnitListTable.tsx index 04988d9..4f63ca3 100644 --- a/src/renderer/components/blocks/tables/ProjectsListTable.tsx +++ b/src/renderer/components/blocks/tables/UntokenizedUnitListTable.tsx @@ -1,14 +1,19 @@ import { DebouncedFunc } from 'lodash'; import React, { useMemo } from 'react'; import { FormattedMessage } from 'react-intl'; -import { Button, Column, DataTable, PageCounter, Pagination, SampleDeepLinkedModal, Tooltip } from '@/components'; +import { Button, Column, DataTable, PageCounter, Pagination, CreateTokenModal, Tooltip } from '@/components'; import { Project } from '@/schemas/Project.schema'; import { Badge } from 'flowbite-react'; import { useUrlHash, useWildCardUrlHash } from '@/hooks'; +import { Unit } from '@/schemas/Unit.schema'; + +export interface UnitWithProject extends Unit { + project: Project; +} interface TableProps { - data: Project[]; - rowActions?: 'edit' | 'transfer'; + data: UnitWithProject[]; + rowActions?: 'tokenize'; isLoading: boolean; currentPage: number; onPageChange: DebouncedFunc<(page: any) => void>; @@ -19,10 +24,7 @@ interface TableProps { totalCount: number; } -/** left in as example of the pattern in which the page is in control of getting data from the api and determining view - * settings and how the data and view settings are cascaded down to the components responsible for displaying the data */ - -const ProjectsListTable: React.FC = ({ +const UntokenizedUnitListTable: React.FC = ({ data, rowActions, isLoading, @@ -50,7 +52,7 @@ const ProjectsListTable: React.FC = ({ ignoreChildEvents: true, ignoreOrderChange: true, render: (row: Project) => { - if (rowActions === 'edit') { + if (rowActions === 'tokenize') { return ; } else { return <>; @@ -147,10 +149,10 @@ const ProjectsListTable: React.FC = ({ } /> {(createProjectModalActive || editProjectModalActive) && ( - + )} ); }; -export { ProjectsListTable }; +export { UntokenizedUnitListTable }; diff --git a/src/renderer/components/blocks/tables/index.ts b/src/renderer/components/blocks/tables/index.ts index d10d5cf..15e7fca 100644 --- a/src/renderer/components/blocks/tables/index.ts +++ b/src/renderer/components/blocks/tables/index.ts @@ -1,2 +1,2 @@ -export * from './ProjectsListTable'; +export * from './UntokenizedUnitListTable'; export * from './SkeletonTable'; diff --git a/src/renderer/components/blocks/tabs/UntokenizedUnitsTab.tsx b/src/renderer/components/blocks/tabs/UntokenizedUnitsTab.tsx index cacd587..d0798c7 100644 --- a/src/renderer/components/blocks/tabs/UntokenizedUnitsTab.tsx +++ b/src/renderer/components/blocks/tabs/UntokenizedUnitsTab.tsx @@ -1,77 +1,151 @@ import { FormattedMessage } from 'react-intl'; -import { ProjectModal, ProjectsListTable, SkeletonTable } from '@/components'; -import React, { useCallback, useEffect } from 'react'; -import { useGetProjectsQuery } from '@/api'; +import { CreateTokenModal, UntokenizedUnitListTable, SkeletonTable, UnitWithProject } from '@/components'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useGetProjectsByIdsImmediateMutation } from '@/api'; import { useColumnOrderHandler, useQueryParamState, useWildCardUrlHash } from '@/hooks'; import { debounce } from 'lodash'; +import { GetUnitsResponse, useGetUntokenizedUnitsQuery } from '@/api/cadt/v1/units.api'; +import { Unit } from '@/schemas/Unit.schema'; + +interface UnitsWithProjectsResult extends Omit { + data: UnitWithProject[]; +} interface PageTabProps { - orgUid: string; search: string; order: string; setOrder: (order: string) => void; - setIsLoading: (isLoading: boolean) => void; } -const UntokenizedUnitsTab: React.FC = ({ - orgUid, - search, - order, - setOrder, - setIsLoading, -}: PageTabProps) => { +const UntokenizedUnitsTab: React.FC = ({ search, order, setOrder }: PageTabProps) => { const [currentPage, setCurrentPage] = useQueryParamState('page', '1'); const handleSetOrder = useColumnOrderHandler(order, setOrder); - const [projectDetailsFragment, projectDetailsModalActive, setProjectModalActive] = useWildCardUrlHash('project'); + const [tokenizeModalFragment, tokenizeModalActive, setTokenizeModalActive] = useWildCardUrlHash('project'); + const [dataLoading, setDataLoading] = useState(); const { - data: projectsData, - isLoading: projectsLoading, - error: projectsError, - } = useGetProjectsQuery({ page: Number(currentPage), orgUid, search, order }); + data: untokenizedUnitsResponse, + isLoading: untokenizedUnitsLoading, + error: untokenizedUnitsError, + } = useGetUntokenizedUnitsQuery({ page: Number(currentPage), search, order }); + const [triggerGetProjects, { data: projectsResponse, isLoading: projectsLoading, error: projectsError }] = + useGetProjectsByIdsImmediateMutation(); useEffect(() => { - setIsLoading(projectsLoading); - }, [projectsLoading, setIsLoading]); + console.log('%%%%%%%%%%% loading status', untokenizedUnitsLoading, projectsLoading); + if (untokenizedUnitsLoading) { + setDataLoading(true); + } else if ( + dataLoading && + !untokenizedUnitsLoading && + untokenizedUnitsResponse && + !projectsLoading && + !projectsResponse + ) { + const projectIds: string[] = untokenizedUnitsResponse.data.reduce( + (projectIds: string[], unit: Unit) => { + if (unit?.warehouseProjectId) { + projectIds.push(unit.warehouseProjectId); + } + return projectIds; + }, + [], + ); + console.log('^^^^^^^^ getting projects'); + triggerGetProjects({ projectIds }); + } else if ( + dataLoading && + !untokenizedUnitsLoading && + !projectsLoading && + untokenizedUnitsResponse && + projectsResponse + ) { + setDataLoading(false); + } + }, [ + dataLoading, + projectsResponse, + projectsLoading, + triggerGetProjects, + untokenizedUnitsResponse, + untokenizedUnitsLoading, + ]); + + console.log('units', untokenizedUnitsResponse); + console.log('projects', projectsResponse); + + const unifiedResult = useMemo(() => { + console.log(projectsResponse && untokenizedUnitsResponse); + if ( + projectsResponse && + untokenizedUnitsResponse && + !untokenizedUnitsError && + !projectsError && + !untokenizedUnitsLoading && + !projectsLoading + ) { + const result: UnitsWithProjectsResult = { ...untokenizedUnitsResponse, data: [] }; + untokenizedUnitsResponse.data.forEach((unit: Unit) => { + const associatedProject = projectsResponse.find( + (project) => project.warehouseProjectId === unit.warehouseProjectId, + ); + if (associatedProject) { + result.data.push({ ...unit, project: associatedProject }); + } else { + console.error('unit did not have associated project'); + } + }); + return result; + } else { + return undefined; + } + }, [ + projectsError, + projectsLoading, + projectsResponse, + untokenizedUnitsError, + untokenizedUnitsLoading, + untokenizedUnitsResponse, + ]); const handlePageChange = useCallback( debounce((page) => setCurrentPage(page), 800), [setCurrentPage], ); - if (projectsLoading) { - return ; + if (untokenizedUnitsError || projectsError) { + return ; } - if (projectsError) { - return ; + if (dataLoading) { + return ; } - if (!projectsData) { + if (!unifiedResult) { return ; } return ( <> - {projectsLoading ? ( + {untokenizedUnitsLoading ? ( ) : ( - setProjectModalActive(true, row.warehouseProjectId)} + onRowClick={(row) => setTokenizeModalActive(true, row.warehouseProjectId)} setOrder={handleSetOrder} order={order} - totalPages={projectsData.pageCount} - totalCount={projectsData.pageCount < 10 ? projectsData.data.length : projectsData.pageCount * 10} + totalPages={unifiedResult.pageCount} + totalCount={unifiedResult.pageCount < 10 ? unifiedResult.data.length : unifiedResult.pageCount * 10} /> )} - {projectDetailsModalActive && ( - setProjectModalActive(false)} - warehouseProjectId={projectDetailsFragment.replace('project-', '')} + {tokenizeModalActive && ( + setTokenizeModalActive(false)} + urlFragmentDerivedData={tokenizeModalFragment.replace('tokenize-', '')} /> )} diff --git a/src/renderer/pages/ListPageSample.tsx b/src/renderer/pages/ListPageSample.tsx deleted file mode 100644 index 2f35eab..0000000 --- a/src/renderer/pages/ListPageSample.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { useQueryParamState, useUrlHash } from '@/hooks'; -import { debounce } from 'lodash'; -import { - Button, - UntokenizedUnitsTab, - ComponentCenteredSpinner, - IndeterminateProgressOverlay, - SearchBox, - Tabs, -} from '@/components'; -import { FormattedMessage } from 'react-intl'; -import { Organization } from '@/schemas/Organization.schema'; -import { useNavigate } from 'react-router-dom'; -import { useGetOrganizationsListQuery } from '@/api'; -// @ts-ignore -import { useGetStagedProjectsQuery } from '@/api/cadt/v1/staging'; - -enum TabTypes { - TOKENIZED, - UNTOKENIZED, -} - -interface ProcessedStagingData { - staged: any[]; - pending: any[]; - failed: any[]; - transfer: any; -} - -const MyProjectsPage: React.FC = () => { - const navigate = useNavigate(); - const [orgUid, setOrgUid] = useQueryParamState('orgUid', undefined); - const [search, setSearch] = useQueryParamState('search', undefined); - const [order, setOrder] = useQueryParamState('order', undefined); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [projectStagedSuccess, setProjectStagedSuccess] = useUrlHash('success-stage-project'); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [commitModalActive, setCommitModalActive] = useUrlHash('commit-staged-items'); - const [, setCreateProjectModalActive] = useUrlHash('create-project'); - const [activeTab, setActiveTab] = useState(TabTypes.UNTOKENIZED); - const [committedDataLoading, setCommittedDataLoading] = useState(false); - const { data: unprocessedStagedProjects, isLoading: stagingDataLoading } = useGetStagedProjectsQuery(); - const { data: organizationsListData, isLoading: organizationsListLoading } = useGetOrganizationsListQuery(); - const myOrganization = useMemo( - () => organizationsListData?.find((org: Organization) => org.isHome), - [organizationsListData], - ); - - const processedStagingData: ProcessedStagingData = useMemo(() => { - const data: ProcessedStagingData = { staged: [], pending: [], failed: [], transfer: undefined }; - if (unprocessedStagedProjects?.forEach) { - unprocessedStagedProjects.forEach((stagedProject: any) => { - if (stagedProject?.table === 'Projects') { - 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); - } else if (stagedProject.commited && stagedProject.isTransfer) { - data.transfer = stagedProject; - } - } - }); - } - return data; - }, [unprocessedStagedProjects]); - - const contentsLoading = useMemo(() => { - return committedDataLoading || stagingDataLoading; - }, [committedDataLoading, stagingDataLoading]); - - useEffect(() => { - if (myOrganization) { - setOrgUid(myOrganization.orgUid); - } - }, [myOrganization, myOrganization?.orgUid, organizationsListData, setOrgUid]); - - useEffect(() => { - if (!myOrganization && !organizationsListLoading) { - navigate('/'); - } - }, [myOrganization, navigate, organizationsListLoading]); - - const handleSearchChange = useCallback( - debounce((event: any) => { - setSearch(event.target.value); - }, 800), - [setSearch, debounce], - ); - - if (!myOrganization || organizationsListLoading) { - return ; - } - - return ( - <> -
- {contentsLoading && } -
- {activeTab === TabTypes.UNTOKENIZED && ( - <> - - - - )} - {activeTab === TabTypes.TOKENIZED && ( - <> - - - )} -
-
- setActiveTab(tab)}> - - -

- } - /> - - - {' (' + String(processedStagingData.staged.length + ') ')} -

- } - /> -
-
-
- {activeTab === TabTypes.UNTOKENIZED && ( - - )} - {activeTab === TabTypes.TOKENIZED &&
TODO: tokenized units tab
} -
-
- - ); -}; - -export { MyProjectsPage }; diff --git a/src/renderer/pages/TokensPage.tsx b/src/renderer/pages/TokensPage.tsx new file mode 100644 index 0000000..e150f53 --- /dev/null +++ b/src/renderer/pages/TokensPage.tsx @@ -0,0 +1,61 @@ +import React, { useCallback, useState } from 'react'; +import { useQueryParamState } from '@/hooks'; +import { debounce } from 'lodash'; +import { UntokenizedUnitsTab, SearchBox, Tabs } from '@/components'; +import { FormattedMessage } from 'react-intl'; + +enum TabTypes { + TOKENIZED, + UNTOKENIZED, +} + +const TokensPage: React.FC = () => { + const [search, setSearch] = useQueryParamState('search', undefined); + const [order, setOrder] = useQueryParamState('order', undefined); + const [activeTab, setActiveTab] = useState(TabTypes.UNTOKENIZED); + + const handleSearchChange = useCallback( + debounce((event: any) => { + setSearch(event.target.value); + }, 800), + [setSearch, debounce], + ); + + return ( + <> +
+
+ +
+
+ setActiveTab(tab)}> + + +

+ } + /> + + +

+ } + /> +
+
+
+ {activeTab === TabTypes.UNTOKENIZED && ( + + )} + {activeTab === TabTypes.TOKENIZED && ( + + )} +
+
+ + ); +}; + +export { TokensPage }; diff --git a/src/renderer/pages/index.ts b/src/renderer/pages/index.ts index 662e452..c790e2a 100644 --- a/src/renderer/pages/index.ts +++ b/src/renderer/pages/index.ts @@ -1,2 +1,2 @@ export * from './ErrorBoundary'; -export * from './ListPageSample'; +export * from './TokensPage'; diff --git a/src/renderer/routes/AppNavigator.tsx b/src/renderer/routes/AppNavigator.tsx index 3315d9f..b6923a5 100644 --- a/src/renderer/routes/AppNavigator.tsx +++ b/src/renderer/routes/AppNavigator.tsx @@ -15,7 +15,7 @@ const AppNavigator: React.FC = () => { /> }> } /> - } /> + } /> } /> diff --git a/src/renderer/store/slices/app/app.initialstate.ts b/src/renderer/store/slices/app/app.initialstate.ts index 46646c0..551254c 100644 --- a/src/renderer/store/slices/app/app.initialstate.ts +++ b/src/renderer/store/slices/app/app.initialstate.ts @@ -8,7 +8,7 @@ export interface AppState { const initialState: AppState = { locale: null, - apiHost: 'http://localhost:31310', + apiHost: 'http://localhost:31311', apiKey: null, configFileLoaded: false, isDarkTheme: false, diff --git a/src/renderer/translations/tokens/en-US.json b/src/renderer/translations/tokens/en-US.json index 7d40d9a..4db4fd3 100644 --- a/src/renderer/translations/tokens/en-US.json +++ b/src/renderer/translations/tokens/en-US.json @@ -13,5 +13,6 @@ "connect": "connect", "unable-to-load-contents": "unable to load contents", "tokenized-units": "tokenized units", - "untokenized-units": "untokenized units" + "untokenized-units": "untokenized units", + "create-token": "create token" } \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 4a888a6..d9726ba 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -15,4 +15,7 @@ export default defineConfig({ // Specify the directory to output the build files outDir: path.resolve(__dirname, 'build/renderer'), }, + server: { + port: 5174, + }, });