@izumisy-tailor/tailor-data-viewer 0.1.0

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.
Files changed (37) hide show
  1. package/README.md +255 -0
  2. package/package.json +47 -0
  3. package/src/component/column-selector.tsx +264 -0
  4. package/src/component/data-table.tsx +428 -0
  5. package/src/component/data-view-tab-content.tsx +324 -0
  6. package/src/component/data-viewer.tsx +280 -0
  7. package/src/component/hooks/use-accessible-tables.ts +22 -0
  8. package/src/component/hooks/use-column-state.ts +281 -0
  9. package/src/component/hooks/use-relation-data.ts +387 -0
  10. package/src/component/hooks/use-table-data.ts +317 -0
  11. package/src/component/index.ts +15 -0
  12. package/src/component/pagination.tsx +56 -0
  13. package/src/component/relation-content.tsx +250 -0
  14. package/src/component/saved-view-context.tsx +145 -0
  15. package/src/component/search-filter.tsx +319 -0
  16. package/src/component/single-record-tab-content.tsx +676 -0
  17. package/src/component/table-selector.tsx +102 -0
  18. package/src/component/types.ts +20 -0
  19. package/src/component/view-save-load.tsx +112 -0
  20. package/src/generator/metadata-generator.ts +461 -0
  21. package/src/lib/utils.ts +6 -0
  22. package/src/providers/graphql-client.ts +31 -0
  23. package/src/styles/theme.css +105 -0
  24. package/src/types/table-metadata.ts +73 -0
  25. package/src/ui/alert.tsx +66 -0
  26. package/src/ui/badge.tsx +46 -0
  27. package/src/ui/button.tsx +62 -0
  28. package/src/ui/card.tsx +92 -0
  29. package/src/ui/checkbox.tsx +30 -0
  30. package/src/ui/collapsible.tsx +31 -0
  31. package/src/ui/dialog.tsx +143 -0
  32. package/src/ui/dropdown-menu.tsx +255 -0
  33. package/src/ui/input.tsx +21 -0
  34. package/src/ui/label.tsx +24 -0
  35. package/src/ui/select.tsx +188 -0
  36. package/src/ui/table.tsx +116 -0
  37. package/src/utils/query-builder.ts +190 -0
@@ -0,0 +1,190 @@
1
+ import type {
2
+ TableMetadata,
3
+ FieldMetadata,
4
+ RelationMetadata,
5
+ } from "../types/table-metadata";
6
+
7
+ /**
8
+ * Convert camelCase to PascalCase
9
+ */
10
+ function toPascalCase(str: string): string {
11
+ return str.charAt(0).toUpperCase() + str.slice(1);
12
+ }
13
+
14
+ /**
15
+ * Relation total info to fetch (for oneToMany relations)
16
+ */
17
+ export interface RelationTotalInfo {
18
+ /** The oneToMany relation metadata */
19
+ relation: RelationMetadata;
20
+ /** The plural form of the target table (e.g., "taskAssignments") */
21
+ targetPluralForm: string;
22
+ }
23
+
24
+ /**
25
+ * Expanded relation info for manyToOne relations to be fetched inline
26
+ */
27
+ export interface ExpandedRelationInfo {
28
+ /** The manyToOne relation metadata */
29
+ relation: RelationMetadata;
30
+ /** Selected fields from the target table */
31
+ selectedFields: string[];
32
+ }
33
+
34
+ /**
35
+ * Options for building a list query
36
+ */
37
+ export interface QueryOptions {
38
+ tableName: string;
39
+ pluralForm: string;
40
+ selectedFields: string[];
41
+ orderBy?: { field: string; direction: "Asc" | "Desc" };
42
+ first?: number;
43
+ after?: string;
44
+ /** oneToMany relations to fetch total count for */
45
+ oneToManyRelationTotals?: RelationTotalInfo[];
46
+ /** manyToOne relations to fetch inline with selected fields */
47
+ expandedManyToOneRelations?: ExpandedRelationInfo[];
48
+ }
49
+
50
+ /**
51
+ * Build a GraphQL list query dynamically based on table metadata
52
+ */
53
+ export function buildListQuery(options: QueryOptions): string {
54
+ const {
55
+ tableName,
56
+ pluralForm,
57
+ selectedFields,
58
+ orderBy,
59
+ first,
60
+ after,
61
+ oneToManyRelationTotals,
62
+ expandedManyToOneRelations,
63
+ } = options;
64
+
65
+ // GraphQL type names need PascalCase
66
+ const typeName = toPascalCase(tableName);
67
+
68
+ // Build order clause if specified
69
+ const orderArg = orderBy
70
+ ? `, order: [{ field: ${typeName}OrderFields.${orderBy.field}, direction: OrderDirection.${orderBy.direction} }]`
71
+ : "";
72
+
73
+ // Build pagination arguments
74
+ const firstArg = first !== undefined ? `, first: ${first}` : "";
75
+ const afterArg = after !== undefined ? `, after: "${after}"` : "";
76
+
77
+ // Build oneToMany relation total fields (fetching only total for display before expansion)
78
+ // These are nested connection queries that get total count for each parent record
79
+ const relationTotalFields =
80
+ oneToManyRelationTotals
81
+ ?.map((info) => {
82
+ return `${info.relation.fieldName}(first: 0) {
83
+ total
84
+ }`;
85
+ })
86
+ .join("\n ") ?? "";
87
+
88
+ // Build expanded manyToOne relation fields (inline object fetching)
89
+ const expandedRelationFields =
90
+ expandedManyToOneRelations
91
+ ?.filter((info) => info.selectedFields.length > 0)
92
+ .map((info) => {
93
+ const nestedFields = info.selectedFields.join("\n ");
94
+ return `${info.relation.fieldName} {
95
+ ${nestedFields}
96
+ }`;
97
+ })
98
+ .join("\n ") ?? "";
99
+
100
+ // Format fields with proper indentation
101
+ const fieldsStr = selectedFields.join("\n ");
102
+ let allFieldsStr = fieldsStr;
103
+ if (relationTotalFields) {
104
+ allFieldsStr = `${allFieldsStr}\n ${relationTotalFields}`;
105
+ }
106
+ if (expandedRelationFields) {
107
+ allFieldsStr = `${allFieldsStr}\n ${expandedRelationFields}`;
108
+ }
109
+
110
+ return `
111
+ query ${typeName}List($query: ${typeName}QueryInput) {
112
+ ${pluralForm}(query: $query${orderArg}${firstArg}${afterArg}) {
113
+ edges {
114
+ node {
115
+ ${allFieldsStr}
116
+ }
117
+ }
118
+ pageInfo {
119
+ hasNextPage
120
+ endCursor
121
+ }
122
+ }
123
+ }
124
+ `.trim();
125
+ }
126
+
127
+ /**
128
+ * Get default selected fields for a table
129
+ * Excludes nested, array, and id fields by default
130
+ */
131
+ export function getDefaultSelectedFields(table: TableMetadata): string[] {
132
+ return table.fields
133
+ .filter(
134
+ (field) =>
135
+ field.type !== "nested" &&
136
+ field.type !== "array" &&
137
+ // Exclude id fields (both 'id' and relation ids like 'taskId')
138
+ field.name !== "id" &&
139
+ !field.name.endsWith("Id"),
140
+ )
141
+ .map((field) => field.name);
142
+ }
143
+
144
+ /**
145
+ * Get all selectable fields (excluding nested types)
146
+ */
147
+ export function getSelectableFields(table: TableMetadata): FieldMetadata[] {
148
+ return table.fields.filter((field) => field.type !== "nested");
149
+ }
150
+
151
+ /**
152
+ * Format a value based on its field type for display
153
+ */
154
+ export function formatFieldValue(value: unknown, field: FieldMetadata): string {
155
+ if (value === null || value === undefined) {
156
+ return "-";
157
+ }
158
+
159
+ // Helper to safely convert unknown to string
160
+ const toStr = (v: unknown): string =>
161
+ typeof v === "string"
162
+ ? v
163
+ : typeof v === "number" || typeof v === "boolean"
164
+ ? v.toString()
165
+ : JSON.stringify(v);
166
+
167
+ switch (field.type) {
168
+ case "datetime":
169
+ return new Date(toStr(value)).toLocaleString("ja-JP");
170
+ case "date":
171
+ return new Date(toStr(value)).toLocaleDateString("ja-JP");
172
+ case "time":
173
+ return toStr(value);
174
+ case "boolean":
175
+ return value ? "はい" : "いいえ";
176
+ case "number":
177
+ return typeof value === "number"
178
+ ? value.toLocaleString("ja-JP")
179
+ : toStr(value);
180
+ case "array":
181
+ return Array.isArray(value)
182
+ ? value.map((v) => toStr(v)).join(", ")
183
+ : toStr(value);
184
+ case "uuid":
185
+ // Show truncated UUID for better readability
186
+ return toStr(value).substring(0, 8) + "...";
187
+ default:
188
+ return toStr(value);
189
+ }
190
+ }