@geniee-inc/sfa-kit 1.6.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/GUIDE.md CHANGED
@@ -570,6 +570,68 @@ export default function Page() {
570
570
  }
571
571
  ```
572
572
 
573
+ ## Record detail links (opt-in)
574
+
575
+ Open the standard SFA/CRM record detail page (詳細 tab) for a record. Links are generated on demand. The kit does not add them automatically. Use only when the user asks for clickable record names, detail links, or navigation to the full record view.
576
+
577
+ ### useRecordDetailUrl
578
+
579
+ Returns a link to the record detail page, or `null` when unavailable (standalone preview, no host detected, or unsupported collection such as `schedules`).
580
+
581
+ ```tsx
582
+ import { useRecordDetailUrl } from '@geniee-inc/sfa-kit';
583
+
584
+ function CompanyRow({ record }: { record: SfaRecord }) {
585
+ const link = useRecordDetailUrl(record.collection_name, record.record_id);
586
+
587
+ return (
588
+ <tr>
589
+ <td>
590
+ {link ? (
591
+ <a href={link.url} target={link.target}>
592
+ {record.title ?? record.record_id}
593
+ </a>
594
+ ) : (
595
+ record.title ?? record.record_id
596
+ )}
597
+ </td>
598
+ </tr>
599
+ );
600
+ }
601
+ ```
602
+
603
+ Returns `{ url: string, target: '_blank' } | null`.
604
+
605
+ - `target` is always `'_blank'` so the record detail page opens in a new browser tab, preserving the user's current Studio page.
606
+ - Returns `null` in standalone preview mode (no host origin to build a URL from), when no `organizationId` is in context, or when the collection does not have a standard record page (e.g. `schedules`).
607
+
608
+ ### buildRecordDetailUrl (advanced)
609
+
610
+ Pure function for building the URL manually when you need full control over the parameters.
611
+
612
+ ```tsx
613
+ import { buildRecordDetailUrl } from '@geniee-inc/sfa-kit';
614
+
615
+ const url = buildRecordDetailUrl({
616
+ hostOrigin: 'https://app.chikyu.net',
617
+ organizationId: '12324',
618
+ collectionName: 'companies',
619
+ recordId: '6a7d9baf07dd37e8d464f2cf',
620
+ });
621
+ // -> 'https://app.chikyu.net/org/12324/company/record/6a7d9baf07dd37e8d464f2cf'
622
+
623
+ // Custom objects
624
+ const customUrl = buildRecordDetailUrl({
625
+ hostOrigin: 'https://app.chikyu.net',
626
+ organizationId: '12324',
627
+ collectionName: 'waste_collection_routes',
628
+ recordId: 'b3e1f8c204aa59d7e612a0b1',
629
+ });
630
+ // -> 'https://app.chikyu.net/org/12324/custom_object/waste_collection_routes/record/b3e1f8c204aa59d7e612a0b1'
631
+ ```
632
+
633
+ Returns `null` for unsupported collections (e.g. `schedules`).
634
+
573
635
  ## Do NOT
574
636
 
575
637
  - Do NOT create your own authentication, login forms, or auth providers. `SfaLoginGate` handles everything.
@@ -579,6 +641,7 @@ export default function Page() {
579
641
  - Do NOT call the API directly with fetch. Use the provided hooks or `useSfaClient()`.
580
642
  - Do NOT add Route Handlers (app/api/) for SFA data. The kit calls the API from the browser.
581
643
  - Do NOT hardcode API base URLs, API keys, or credentials anywhere.
644
+ - Do NOT add record detail links automatically. They are opt-in: generate them only when the user asks (e.g. "make the names clickable", "add links to records"), or ask the user for confirmation before adding them. In standalone preview mode, links are unavailable (`useRecordDetailUrl` returns `null`). The `schedules` collection does not have a standard record detail page; if asked, tell the user that schedule links are not yet supported.
582
645
  - Do NOT import from `@mui/material`, `@emotion`, or `@ag-grid`. This package has zero external UI dependencies.
583
646
  - Do NOT nest SfaLoginGate inside another SfaLoginGate or SfaProvider.
584
647
  - Do NOT hardcode a list of available objects. Discover from the user's request or `useDescribe`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geniee-inc/sfa-kit",
3
- "version": "1.6.0",
3
+ "version": "1.7.1",
4
4
  "description": "React hooks and components for building pages that connect to GENIEE SFA/CRM data.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/index.d.ts CHANGED
@@ -27,3 +27,6 @@ export { ErrorBoundary } from './lib/components/ErrorBoundary';
27
27
  export { EmptyState } from './lib/components/EmptyState';
28
28
  export type { SfaAuth, SfaApiMode, SfaClientConfig, SfaRecord, SfaListResponse, SfaListParams, SfaFieldValue, SfaFieldKind, SfaFieldValueMapping, FieldGroupSpec, FieldGroupLayout, SfaFieldDescriptor, SfaFieldOption, SfaRelationDescriptor, SfaObjectDetail, SfaObjectSummary, SfaObjectCatalog, SfaObjectPermissions, SfaObjectOrigin, SfaRelationshipKind, SfaCardinality, SfaDescribeResult, SfaFieldTypeDescriptor, SfaFieldTypeCatalog, SfaFilterOperator, SfaCreateOptions, SfaFieldError, } from './lib/provider/types';
29
29
  export type { SfaSession, AuthResult, OrganizationChoice, } from './lib/auth/types';
30
+ export { buildRecordDetailUrl, resolveHostOrigin, } from './lib/navigation/record-detail-url';
31
+ export type { RecordDetailUrlParams, RecordDetailLink, } from './lib/navigation/record-detail-url';
32
+ export { useRecordDetailUrl } from './lib/hooks/useRecordDetailUrl';
package/src/index.js CHANGED
@@ -19,3 +19,5 @@ export { RecordView } from './lib/components/RecordView';
19
19
  export { RecordList } from './lib/components/RecordList';
20
20
  export { ErrorBoundary } from './lib/components/ErrorBoundary';
21
21
  export { EmptyState } from './lib/components/EmptyState';
22
+ export { buildRecordDetailUrl, resolveHostOrigin, } from './lib/navigation/record-detail-url';
23
+ export { useRecordDetailUrl } from './lib/hooks/useRecordDetailUrl';
@@ -0,0 +1,22 @@
1
+ import type { RecordDetailLink } from '../navigation/record-detail-url';
2
+ /**
3
+ * @description Returns a link to the SFA/CRM record detail page (詳細 tab),
4
+ * or `null` when unavailable (standalone preview, missing org ID, or
5
+ * unsupported collection like `schedules`).
6
+ *
7
+ * Reads the organization ID from `SfaContext` and the host origin from
8
+ * `document.referrer` (the SFA parent frame). No configuration needed.
9
+ *
10
+ * The returned `target` is always `'_blank'` so the record opens in a new
11
+ * browser tab, preserving the user's current Studio page context.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * const link = useRecordDetailUrl('companies', 'rec-123');
16
+ * if (link) {
17
+ * return <a href={link.url} target={link.target}>{name}</a>;
18
+ * }
19
+ * return <span>{name}</span>;
20
+ * ```
21
+ */
22
+ export declare function useRecordDetailUrl(collectionName: string, recordId: string): RecordDetailLink | null;
@@ -0,0 +1,42 @@
1
+ import { useMemo } from 'react';
2
+ import { buildRecordDetailUrl, resolveHostOrigin, } from '../navigation/record-detail-url';
3
+ import { useSfaContext } from '../provider/useSfaClient';
4
+ /**
5
+ * @description Returns a link to the SFA/CRM record detail page (詳細 tab),
6
+ * or `null` when unavailable (standalone preview, missing org ID, or
7
+ * unsupported collection like `schedules`).
8
+ *
9
+ * Reads the organization ID from `SfaContext` and the host origin from
10
+ * `document.referrer` (the SFA parent frame). No configuration needed.
11
+ *
12
+ * The returned `target` is always `'_blank'` so the record opens in a new
13
+ * browser tab, preserving the user's current Studio page context.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * const link = useRecordDetailUrl('companies', 'rec-123');
18
+ * if (link) {
19
+ * return <a href={link.url} target={link.target}>{name}</a>;
20
+ * }
21
+ * return <span>{name}</span>;
22
+ * ```
23
+ */
24
+ export function useRecordDetailUrl(collectionName, recordId) {
25
+ const { organizationId } = useSfaContext();
26
+ return useMemo(() => {
27
+ const hostOrigin = resolveHostOrigin();
28
+ if (hostOrigin == null || organizationId == null) {
29
+ return null;
30
+ }
31
+ const url = buildRecordDetailUrl({
32
+ hostOrigin,
33
+ organizationId,
34
+ collectionName,
35
+ recordId,
36
+ });
37
+ if (url == null) {
38
+ return null;
39
+ }
40
+ return { url, target: '_blank' };
41
+ }, [organizationId, collectionName, recordId]);
42
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @description Builds URLs for SFA/CRM record detail pages (詳細 tab).
3
+ *
4
+ * The URL pattern varies between standard objects (singular route key) and
5
+ * custom objects (collection name in a `/custom_object/` segment). This
6
+ * module encapsulates that mapping so callers only need a collection name,
7
+ * record ID, host origin, and organization ID.
8
+ *
9
+ * Source of truth for the standard-object route map:
10
+ * apps/axiom-web/src/pages/org/[organizationId]/[singularName]/record/[recordId].tsx
11
+ * and DEFAULT_COLLECTION_NAME_REV_MAP in axiom-web.
12
+ */
13
+ export interface RecordDetailUrlParams {
14
+ /** The SFA/CRM host origin (e.g. "https://app.chikyu.net"). */
15
+ readonly hostOrigin: string;
16
+ /** The organization ID the record belongs to. */
17
+ readonly organizationId: string;
18
+ /** The plural collection name (e.g. "companies", "business_discussions"). */
19
+ readonly collectionName: string;
20
+ /** The record's unique ID. */
21
+ readonly recordId: string;
22
+ }
23
+ export interface RecordDetailLink {
24
+ /** The full URL to the record detail page. */
25
+ readonly url: string;
26
+ /**
27
+ * Suggested link target. Always `'_blank'` so the record detail page opens
28
+ * in a new browser tab, preserving the user's current Studio page context.
29
+ * The SFA iframe sandbox includes `allow-popups` to permit this.
30
+ */
31
+ readonly target: '_blank';
32
+ }
33
+ /**
34
+ * @description Builds the full URL for a record's detail page in SFA/CRM.
35
+ *
36
+ * Returns `null` when the collection is not supported (e.g. `schedules`).
37
+ *
38
+ * Standard objects use: `{hostOrigin}/org/{orgId}/{singularKey}/record/{recordId}`
39
+ * Custom objects use: `{hostOrigin}/org/{orgId}/custom_object/{collectionName}/record/{recordId}`
40
+ */
41
+ export declare function buildRecordDetailUrl(params: RecordDetailUrlParams): string | null;
42
+ /**
43
+ * @description Resolves the SFA/CRM host origin from the embedding context.
44
+ *
45
+ * In embed mode (page running inside an SFA iframe), `document.referrer`
46
+ * contains the parent page's URL, from which we extract the origin.
47
+ * This is the same pattern used by `resolveTrustedOrigin` in SfaLoginGate.
48
+ *
49
+ * Returns `null` when:
50
+ * - Running in SSR (no `window`)
51
+ * - Not running inside an iframe (standalone mode)
52
+ * - The referrer is empty or malformed
53
+ */
54
+ export declare function resolveHostOrigin(): string | null;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * @description Builds URLs for SFA/CRM record detail pages (詳細 tab).
3
+ *
4
+ * The URL pattern varies between standard objects (singular route key) and
5
+ * custom objects (collection name in a `/custom_object/` segment). This
6
+ * module encapsulates that mapping so callers only need a collection name,
7
+ * record ID, host origin, and organization ID.
8
+ *
9
+ * Source of truth for the standard-object route map:
10
+ * apps/axiom-web/src/pages/org/[organizationId]/[singularName]/record/[recordId].tsx
11
+ * and DEFAULT_COLLECTION_NAME_REV_MAP in axiom-web.
12
+ */
13
+ /**
14
+ * Maps standard (default) collection names (plural) to their singular URL
15
+ * route key used in axiom-web's `/org/{orgId}/{singularKey}/record/{recordId}`
16
+ * route.
17
+ *
18
+ * Sync reference: `DEFAULT_COLLECTION_NAME_REV_MAP` in axiom-web.
19
+ * This list changes only when SFA adds new default object types.
20
+ */
21
+ const STANDARD_OBJECT_ROUTE_MAP = {
22
+ business_discussions: 'business_discussion',
23
+ companies: 'company',
24
+ customers: 'customer',
25
+ prospects: 'prospect',
26
+ activity_histories: 'activity_history',
27
+ tasks: 'task',
28
+ merchandises: 'merchandise',
29
+ opportunity_merchandises: 'opportunity_merchandise',
30
+ // Alt objects (ALT_COLLECTION_NAME_REV_MAP in axiom-web)
31
+ projects: 'project',
32
+ };
33
+ /**
34
+ * Collections that have no standard record detail page. `buildRecordDetailUrl`
35
+ * returns `null` for these.
36
+ *
37
+ * - `schedules`: uses a query-param based URL (`/schedule?selectedSchedule=...`),
38
+ * not the standard `/record/{id}` pattern. Support may be added later.
39
+ */
40
+ const UNSUPPORTED_COLLECTIONS = new Set(['schedules']);
41
+ /**
42
+ * @description Builds the full URL for a record's detail page in SFA/CRM.
43
+ *
44
+ * Returns `null` when the collection is not supported (e.g. `schedules`).
45
+ *
46
+ * Standard objects use: `{hostOrigin}/org/{orgId}/{singularKey}/record/{recordId}`
47
+ * Custom objects use: `{hostOrigin}/org/{orgId}/custom_object/{collectionName}/record/{recordId}`
48
+ */
49
+ export function buildRecordDetailUrl(params) {
50
+ const { hostOrigin, organizationId, collectionName, recordId } = params;
51
+ if (UNSUPPORTED_COLLECTIONS.has(collectionName)) {
52
+ return null;
53
+ }
54
+ let origin;
55
+ try {
56
+ const parsed = new URL(hostOrigin);
57
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
58
+ return null;
59
+ }
60
+ origin = parsed.origin;
61
+ }
62
+ catch (_a) {
63
+ return null;
64
+ }
65
+ const singularKey = STANDARD_OBJECT_ROUTE_MAP[collectionName];
66
+ if (singularKey != null) {
67
+ return `${origin}/org/${encodeURIComponent(organizationId)}/${singularKey}/record/${encodeURIComponent(recordId)}`;
68
+ }
69
+ // Custom object
70
+ return `${origin}/org/${encodeURIComponent(organizationId)}/custom_object/${encodeURIComponent(collectionName)}/record/${encodeURIComponent(recordId)}`;
71
+ }
72
+ /**
73
+ * @description Resolves the SFA/CRM host origin from the embedding context.
74
+ *
75
+ * In embed mode (page running inside an SFA iframe), `document.referrer`
76
+ * contains the parent page's URL, from which we extract the origin.
77
+ * This is the same pattern used by `resolveTrustedOrigin` in SfaLoginGate.
78
+ *
79
+ * Returns `null` when:
80
+ * - Running in SSR (no `window`)
81
+ * - Not running inside an iframe (standalone mode)
82
+ * - The referrer is empty or malformed
83
+ */
84
+ export function resolveHostOrigin() {
85
+ try {
86
+ if (typeof window === 'undefined')
87
+ return null;
88
+ // Only resolve when running inside an iframe. In standalone mode,
89
+ // document.referrer may contain an external origin (e.g. the page
90
+ // that navigated here), which would produce incorrect links.
91
+ if (window.parent === window)
92
+ return null;
93
+ const referrer = document.referrer;
94
+ if (!referrer)
95
+ return null;
96
+ return new URL(referrer).origin;
97
+ }
98
+ catch (_a) {
99
+ // Cross-origin window.parent access throws (embedded), or malformed referrer
100
+ // If we can't access window.parent, we're in a cross-origin iframe,
101
+ // so fall through to read the referrer.
102
+ }
103
+ try {
104
+ const referrer = document.referrer;
105
+ if (!referrer)
106
+ return null;
107
+ return new URL(referrer).origin;
108
+ }
109
+ catch (_b) {
110
+ return null;
111
+ }
112
+ }
@@ -1 +1 @@
1
- export declare const SFA_KIT_VERSION = "1.6.0";
1
+ export declare const SFA_KIT_VERSION = "1.7.1";
@@ -1 +1,3 @@
1
- export const SFA_KIT_VERSION = "1.6.0";
1
+ // このファイルは libs/sfa-kit/scripts/sync-version.cjs が生成する。
2
+ // 手で編集しないこと。バージョンを上げるときは libs/sfa-kit/package.json を変更する。
3
+ export const SFA_KIT_VERSION = '1.7.1';