diff --git a/.github/workflows/manual-deploy.yml b/.github/workflows/manual-deploy.yml
index bec5927a5..1860b4643 100644
--- a/.github/workflows/manual-deploy.yml
+++ b/.github/workflows/manual-deploy.yml
@@ -17,6 +17,7 @@ on:
- punch
- scopechangerequest
- workorder
+ - transfer-poc
- '*'
permissions:
actions: read
diff --git a/apps/transfer/package.json b/apps/transfer/package.json
new file mode 100644
index 000000000..93fe1e9af
--- /dev/null
+++ b/apps/transfer/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "transfer-poc",
+ "displayName": "Transfer",
+ "version": "0.0.1",
+ "main": "/src/main.tsx",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "fusion-framework-cli app dev",
+ "dev:local": "fusion-framework-cli app dev",
+ "build": "tsc -b -f",
+ "pr:deploy": "tsx ../../github-action/src/releasePr.ts release",
+ "fprd:deploy": "tsx ../../github-action/src/releaseMain.ts release"
+ },
+ "dependencies": {
+ "@cc-components/shared": "workspace:^"
+ }
+}
+
diff --git a/apps/transfer/src/Accordion.tsx b/apps/transfer/src/Accordion.tsx
new file mode 100644
index 000000000..c0f602940
--- /dev/null
+++ b/apps/transfer/src/Accordion.tsx
@@ -0,0 +1,16 @@
+import { Accordion } from '@equinor/eds-core-react'
+
+export type AccordionSectionProps = {
+ header: string;
+ description: string;
+}
+export const AccordionSection = ({ header, description }: AccordionSectionProps) => (
+
+
+
+ {header}
+
+
+ {description}
+
+)
diff --git a/apps/transfer/src/MaintenanceHistory.tsx b/apps/transfer/src/MaintenanceHistory.tsx
new file mode 100644
index 000000000..da070636f
--- /dev/null
+++ b/apps/transfer/src/MaintenanceHistory.tsx
@@ -0,0 +1,33 @@
+import React from "react"
+import { StateProps } from "./main"
+import { Accordion, Button, Icon, Typography } from "@equinor/eds-core-react"
+import { AccordionSection } from "./Accordion"
+import { tokens } from "@equinor/eds-tokens"
+
+type MaintenanceHistoryProps = {
+} & StateProps
+
+export function MaintenanceHistory(props: MaintenanceHistoryProps) {
+
+ return (
+
+
<>{props.isCompleted && }>Maintenance History
+
+
+
+
+
+
+
+
+ Please confirm that the maintenance history for the tags has been handed over to operations.
+
+
{
+ props.next()
+ }}>Continue
+
+
+ )
+}
+
+
diff --git a/apps/transfer/src/Scoping.tsx b/apps/transfer/src/Scoping.tsx
new file mode 100644
index 000000000..51a623e64
--- /dev/null
+++ b/apps/transfer/src/Scoping.tsx
@@ -0,0 +1,111 @@
+import { ClientGrid } from '@equinor/workspace-ag-grid';
+import React, { useEffect, useState } from 'react';
+import { Autocomplete, Button, CircularProgress, Icon, Typography } from '@equinor/eds-core-react'
+import { commpkgQuery, tagQuery } from './famqueries';
+import { FamCommPkg, Famtag } from './types';
+import { useFamQuery } from './useFamQuery';
+import { tagsDef } from './tagcolumns'
+import { StateProps } from './main';
+import { tokens } from '@equinor/eds-tokens';
+import { useMutation } from '@tanstack/react-query';
+
+const mccr_status_map = {
+ 0: "OS",
+ 1: "PA",
+ 2: "PB",
+ 3: "OK"
+}
+
+function useCommissioningPackages(facility: string) {
+ return useFamQuery(["commpkgs"], commpkgQuery(facility))
+}
+
+export type ScopingProps = {
+ tags: Famtag[] | null;
+ setTags: (tags: Famtag[]) => void;
+} & StateProps
+
+export function Scoping(props: ScopingProps) {
+ const [commpkg, setCommPkg] = useState(null)
+
+ const { isLoading: tagsLoading, data: tagsData } = useFamQuery(["tags", commpkg?.commissioningPackageNo ?? ""], tagQuery(commpkg?.commissioningPackageNo ?? "", props.facility), !!commpkg)
+ const tags = tagsData?.map(s => ({ ...s, mccrStatus: mccr_status_map[s.worstStatus] })) as Famtag[]
+ const [reports, setReports] = useState([])
+
+
+ const { isPending, mutateAsync } = useMutation({
+ mutationFn: async () => {
+ const promise = Promise.all([
+ new Promise((res) => setTimeout(() => {
+ setReports(s => [...s, "MC21 - Commissioning package for certificate"]);
+ res("")
+ }, 500)),
+ new Promise((res) => setTimeout(() => {
+ setReports(s => [...s, "DCP01 - Dynamic Commissioning Procedure"]);
+ res("")
+ }, 1000)),
+ new Promise((res) => setTimeout(() => {
+ setReports(s => [...s, "MC22 - CPCL/RL content by comm.pkg"]);
+ res("")
+ }, 1500)),
+ new Promise((res) => setTimeout(() => {
+ setReports(s => [...s, "MC84 - Punchlist report for certificate"]);
+ res("")
+ }, 2000)),
+
+ ]);
+ return await promise
+ }
+ })
+
+ useEffect(() => {
+ props.setTags(tags)
+ }, [commpkg?.commissioningPackageNo, tagsData])
+
+ const { isLoading, data, error } = useCommissioningPackages("JCA")
+
+ const isAllTagsValid = tags?.some(s => s.worstStatus < 2)
+
+ return (
+
+
<>{props.isCompleted && }>Scoping
+
+
{
+ setCommPkg(a.selectedItems[0] ?? null)
+ setReports([])
+ mutateAsync()
+ }} optionLabel={s => s.commissioningPackageNo} selectedOptions={[]} multiple={false} options={data ?? []} label={"Search commpkg for transfer"} />
+ {isLoading && ( )}
+
+
Tags {tagsLoading && ( )}
+
+
+
+
+ <>{commpkg && (
+ <>
+
Reports
+ {isPending && (
Generating reports....
)}
+
+ >
+ )}>
+
+
{
+ props.next()
+ }}>Initiate RFOC certificate
+
+
+
+ )
+}
+
+type ReportsProps = {
+ reports: string[]
+}
+const Reports = (props: ReportsProps) => {
+ return (
+
+ {props.reports.map(s =>
{s}
)}
+
+ )
+}
diff --git a/apps/transfer/src/Signing.tsx b/apps/transfer/src/Signing.tsx
new file mode 100644
index 000000000..31426591c
--- /dev/null
+++ b/apps/transfer/src/Signing.tsx
@@ -0,0 +1,67 @@
+import React, { useState } from "react"
+import { StateProps } from "./main"
+import { Button, CircularProgress, Icon, Typography } from "@equinor/eds-core-react"
+import { tokens } from "@equinor/eds-tokens"
+import { useMutation } from "@tanstack/react-query"
+
+export type SigningProps = {
+} & StateProps
+
+
+const signers = [
+ "Ola Nordmann",
+ "Jørn Olafsen",
+ "Børje Larsen",
+ "Cathrine Iversen",
+]
+
+export function Signing(props: SigningProps) {
+ const [signIndex, setsignIndex] = useState(0)
+ const [signPending, setSignPending] = useState(false);
+
+ const initiateSign = async () => {
+ setSignPending(true)
+ await new Promise((res) => {
+ setTimeout(() => {
+ setsignIndex(s => s + 1)
+ res(false)
+ }, 500)
+ })
+ setSignPending(false)
+ }
+
+ const { isPending, isSuccess, mutateAsync } = useMutation({
+ mutationFn: async () => {
+ return new Promise((res) => setTimeout(() => res(true), 5000))
+ }
+ })
+
+ return (
+
+
<>{props.isCompleted && }>Signing
+
+ {signers.map((s, i) => <>
+ {s} {i < signIndex ? : initiateSign()} disabled={!props.isActive || i > signIndex}>{(signPending && i == signIndex) ? : "Sign"} }>)}
+
+ {isPending && (
+
+
+ Setting tag status to asbuilt in STID
+
+ )}
+
+ {isSuccess && (
+
+
+ Status updated to asbuilt in STID
+
+ )}
+
{
+ await mutateAsync()
+ props.next()
+ }}>Continue
+
+
+
+ )
+}
diff --git a/apps/transfer/src/famqueries.ts b/apps/transfer/src/famqueries.ts
new file mode 100644
index 000000000..c917e4f22
--- /dev/null
+++ b/apps/transfer/src/famqueries.ts
@@ -0,0 +1,21 @@
+export const tagQuery = (commpkgNo: string, facility: string) => `
+SELECT tag.tagNo,min(c.tagStatus) as worstStatus
+FROM Completion.CompletionTag_v3 as tag
+JOIN (
+ SELECT
+ sourceIdentity,
+ tagId,
+ CASE
+ WHEN status='OS' THEN 0
+ WHEN status='PA' THEN 1
+ WHEN status='PB' THEN 2
+ ELSE 3
+ END as tagStatus
+ FROM Completion.CompletionChecklist_v2
+) as c ON c.tagId = tag.sourceIdentity
+WHERE tag.commIssioningPackageNo = '${commpkgNo}' and tag.facility = '${facility}'
+GROUP BY tag.tagNo
+`
+
+export const commpkgQuery = (facility: string) => `SELECT * FROM Completion.CommissioningPackage_v3 where facility = '${facility}'`;
+
diff --git a/apps/transfer/src/framework-config.ts b/apps/transfer/src/framework-config.ts
new file mode 100644
index 000000000..50c3200ef
--- /dev/null
+++ b/apps/transfer/src/framework-config.ts
@@ -0,0 +1,29 @@
+import {
+ ComponentRenderArgs,
+ IAppConfigurator,
+} from '@equinor/fusion-framework-react-app';
+import { enableContext } from '@equinor/fusion-framework-react-module-context';
+import buildQuery from 'odata-query';
+
+export const configure = async (config: IAppConfigurator, c: ComponentRenderArgs) => {
+ enableContext(config, async (builder) => {
+ builder.setContextType(['ProjectMaster', 'Facility']);
+ builder.setContextParameterFn(({ search, type }) => {
+ return buildQuery({
+ search,
+ filter: {
+ type: {
+ in: type,
+ },
+ },
+ });
+ });
+ });
+
+ config.configureHttpClient('cc-api', {
+ baseUri: "https://famapi.equinor.com",
+ defaultScopes: ["api://958bef40-48c3-496e-bc0b-0fe5783f196b/access_as_user"],
+ });
+
+};
+
diff --git a/apps/transfer/src/main.tsx b/apps/transfer/src/main.tsx
new file mode 100644
index 000000000..b8869a5f9
--- /dev/null
+++ b/apps/transfer/src/main.tsx
@@ -0,0 +1,66 @@
+import { createRender } from '@cc-components/shared';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import React, { useState } from 'react';
+import { Famtag } from './types';
+import { configure } from './framework-config';
+import { check_circle_outlined, library_pdf } from '@equinor/eds-icons';
+import { Signing } from './Signing';
+import { MaintenanceHistory } from './MaintenanceHistory';
+import { Scoping } from './Scoping';
+import { Icon, Typography } from '@equinor/eds-core-react';
+
+Icon.add({
+ library_pdf,
+ check_circle_outlined
+})
+
+function Transfer() {
+ const [facility] = useState("JCA")
+ const [state, setState] = useState<"SCOPING" | "SIGNING" | "MAINTENANCE HISTORY" | "ARCHIVED">("SCOPING");
+ const [tags, setTags] = useState(null);
+
+ return (
+
+
+ setState("SIGNING")} facility={facility} />
+ setState("MAINTENANCE HISTORY")} facility={facility} />
+ setState("ARCHIVED")} facility={facility} />
+
+
+ )
+}
+
+export type StateProps = {
+ next: () => void;
+ isActive: boolean;
+ isCompleted: boolean;
+ facility: string;
+}
+
+type ArchivedProps = {
+} & Omit
+
+function Archived(props: ArchivedProps) {
+
+ return (
+
+ Archived
+
+
+ )
+}
+
+const queryclient = new QueryClient()
+const TransferApp = () => {
+ return (
+
+
+
+ );
+};
+
+
+export const render = createRender(TransferApp, configure, 'Transfer');
+
+export default render;
+
diff --git a/apps/transfer/src/tagcolumns.tsx b/apps/transfer/src/tagcolumns.tsx
new file mode 100644
index 000000000..8f20fd42a
--- /dev/null
+++ b/apps/transfer/src/tagcolumns.tsx
@@ -0,0 +1,26 @@
+import { ColDef, ICellRendererProps } from "@equinor/workspace-fusion/dist/lib/integrations/grid";
+import { Famtag } from "./types";
+import { BaseStatus, StatusCell, statusColorMap } from "@cc-components/shared";
+
+export const tagsDef: ColDef[] = [
+ {
+ headerName: "TagNo",
+ valueGetter: (s) => s.data?.tagNo
+ },
+ {
+ headerName: "MCCR status",
+ valueGetter: (s) => s.data?.mccrStatus,
+ cellRenderer: (props: ICellRendererProps) => {
+ if (!props.value) return null;
+ const statusColor = statusColorMap[(props.value as BaseStatus) ?? 'OS'];
+ return (
+ ({
+ style: { backgroundColor: statusColor },
+ })}
+ />
+ );
+ },
+ }
+]
diff --git a/apps/transfer/src/types.ts b/apps/transfer/src/types.ts
new file mode 100644
index 000000000..c7df95bc4
--- /dev/null
+++ b/apps/transfer/src/types.ts
@@ -0,0 +1,32 @@
+export interface FamCommPkg {
+ messageTimestamp: string
+ famBehaviorTime: string
+ sourceName: string
+ sourceIdentity: string
+ facility: string
+ project: string
+ location: string
+ isVoided: boolean
+ commissioningPackageNo: string
+ description: string
+ descriptionOfWork: string
+ remark: string
+ commissioningPhase: string
+ progress: any
+ demolition: boolean
+ priority1: string
+ priority2: string
+ priority3: string
+ identifier: string
+ status: string
+ dynamicCommissioningStatus: string
+ responsible: string
+ createdDate: string
+ updatedDate: string
+ urlId: string
+}
+export interface Famtag {
+ tagNo: string
+ worstStatus: 0 | 1 | 2 | 3
+ mccrStatus: "OS" | "OK" | "PA" | "PB"
+}
diff --git a/apps/transfer/src/useFamQuery.ts b/apps/transfer/src/useFamQuery.ts
new file mode 100644
index 000000000..04c4bea04
--- /dev/null
+++ b/apps/transfer/src/useFamQuery.ts
@@ -0,0 +1,24 @@
+import { useHttpClient } from "@equinor/fusion-framework-react/http"
+import { useQuery } from "@tanstack/react-query"
+
+export const useFamQuery = (queryKey: string[], query: string, enabled: boolean = true) => {
+ const client = useHttpClient("cc-api")
+ return useQuery({
+ enabled,
+ queryKey,
+ queryFn: async () => {
+ const res = await client.fetch("/v2/dynamic", {
+ method: "POST",
+ headers: {
+ ["content-type"]: "application/json"
+ },
+ body: JSON.stringify({
+ pagination: null,
+ options: null,
+ query: query
+ })
+ })
+ return (await res.json()).data as T
+ }
+ })
+}
diff --git a/apps/transfer/tsconfig.json b/apps/transfer/tsconfig.json
new file mode 100644
index 000000000..cc3d460ea
--- /dev/null
+++ b/apps/transfer/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "allowJs": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "files": [],
+ "include": [],
+ "references": []
+}
+
diff --git a/apps/transfer/vite.config.ts b/apps/transfer/vite.config.ts
new file mode 100644
index 000000000..2195c8418
--- /dev/null
+++ b/apps/transfer/vite.config.ts
@@ -0,0 +1,27 @@
+import { defineConfig } from 'vite';
+import EnvironmentPlugin from 'vite-plugin-environment';
+import { InjectProcessPlugin } from '../../patches/3d-patch.ts';
+
+export default defineConfig({
+ plugins: [
+ EnvironmentPlugin({
+ NODE_ENV: 'production',
+ }),
+ ],
+ appType: 'custom',
+ build: {
+ emptyOutDir: true,
+ rollupOptions: {
+ plugins: [InjectProcessPlugin],
+ output: {
+ inlineDynamicImports: true,
+ },
+ },
+ lib: {
+ entry: './src/main.tsx',
+ fileName: 'app-bundle',
+ formats: ['es'],
+ },
+ },
+});
+
diff --git a/github-action/src/utils/uploadBundle.ts b/github-action/src/utils/uploadBundle.ts
index 40392a070..d95391a48 100644
--- a/github-action/src/utils/uploadBundle.ts
+++ b/github-action/src/utils/uploadBundle.ts
@@ -34,6 +34,7 @@ export async function uploadBundle(
notice(`bundle uploaded with status code ${r.message.statusCode}`);
if (r.message.statusCode !== 200) {
logInfo(`Failed to upload ${appKey}, code: ${r.message.statusCode}`, 'Red');
+ console.log(r.message.read().toString())
throw new Error('Bundle failed to upload, fatal error');
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 196fd9261..e8a5ac942 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -56,7 +56,7 @@ importers:
version: 8.3.0(@types/react@18.3.2)(react@18.2.0)
'@equinor/workspace-fusion':
specifier: 9.0.8
- version: 9.0.8(@ag-grid-enterprise/core@31.2.1)(@babel/core@7.24.5)(@types/react@18.3.2)(@types/sortablejs@1.15.8)(immer@9.0.21)(react-is@18.3.1)
+ version: 9.0.8(@ag-grid-community/styles@31.2.0)(@ag-grid-enterprise/core@31.2.1)(@babel/core@7.24.5)(@types/react@18.3.2)(@types/sortablejs@1.15.8)(immer@9.0.21)(react-is@18.3.1)
'@microsoft/applicationinsights-web':
specifier: ^3.2.0
version: 3.2.0(tslib@2.6.2)
@@ -272,6 +272,12 @@ importers:
specifier: workspace:^
version: link:../../libs/swcrapp
+ apps/transfer:
+ dependencies:
+ '@cc-components/shared':
+ specifier: workspace:^
+ version: link:../../libs/shared
+
apps/workorder:
dependencies:
'@cc-components/shared':
@@ -2205,6 +2211,7 @@ packages:
'@humanwhocodes/config-array@0.11.14':
resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
engines: {node: '>=10.10.0'}
+ deprecated: Use @eslint/config-array instead
'@humanwhocodes/module-importer@1.0.1':
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
@@ -2212,6 +2219,7 @@ packages:
'@humanwhocodes/object-schema@2.0.3':
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
+ deprecated: Use @eslint/object-schema instead
'@icons/material@0.2.4':
resolution: {integrity: sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==}
@@ -10377,7 +10385,7 @@ snapshots:
- '@types/sortablejs'
- supports-color
- '@equinor/workspace-fusion@9.0.8(@ag-grid-enterprise/core@31.2.1)(@babel/core@7.24.5)(@types/react@18.3.2)(@types/sortablejs@1.15.8)(immer@9.0.21)(react-is@18.3.1)':
+ '@equinor/workspace-fusion@9.0.8(@ag-grid-community/styles@31.2.0)(@ag-grid-enterprise/core@31.2.1)(@babel/core@7.24.5)(@types/react@18.3.2)(@types/sortablejs@1.15.8)(immer@9.0.21)(react-is@18.3.1)':
dependencies:
'@equinor/eds-core-react': 0.37.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
'@equinor/eds-icons': 0.21.0