+import { useCall, Button } from "frappe-ui";
+import { computed } from "vue";
+import FieldRenderer from "../builder/FieldRenderer.vue";
+import { mapDoctypeFieldForForm } from "@/utils/form_fields";
+
+type Row = {
+ [key: string]: any;
+};
+
+const rows = defineModel
({ default: [] });
+
+type props = {
+ inEditMode: boolean;
+ doctype: string;
+};
+
+const props = defineProps();
+
+const columnResource = useCall({
+ url: "forms_pro.api.form.get_doctype_fields",
+ baseUrl: "/api/v2/method/",
+ params: {
+ doctype: props.doctype,
+ },
+});
+
+const columns = computed(() => {
+ const data = columnResource.data;
+ if (!Array.isArray(data)) {
+ return [];
+ }
+ return data.map((column: any) => ({
+ label: column.label,
+ key: column.fieldname,
+ ...column,
+ fieldtype: mapDoctypeFieldForForm(column.fieldtype) ?? "Data",
+ }));
+});
+
+function addRow() {
+ const newRow = columns.value.reduce((acc, column) => {
+ acc[column.key] = null;
+ return acc;
+ }, {} as Row);
+ rows.value = [...(rows.value ?? []), newRow];
+}
+
+function removeRow(index: number) {
+ rows.value = rows.value?.filter((_, i) => i !== index) ?? [];
+}
+
+function updateCell(rowIndex: number, key: string, value: unknown) {
+ const list = rows.value ?? [];
+ if (rowIndex < 0 || rowIndex >= list.length) return;
+ rows.value = list.map((row, i) => (i === rowIndex ? { ...row, [key]: value } : row));
+}
+
+
+
+
+
No rows added
+
Add an item to get started
+
+
+
+
+
+
+
+
diff --git a/frontend/src/types/formfield.ts b/frontend/src/types/formfield.ts
index 8466304..32c26ca 100644
--- a/frontend/src/types/formfield.ts
+++ b/frontend/src/types/formfield.ts
@@ -16,6 +16,7 @@ export enum FormFieldTypes {
Link = "Link",
Checkbox = "Checkbox",
Rating = "Rating",
+ Table = "Table",
}
export type FormField = {
diff --git a/frontend/src/utils/form_fields.ts b/frontend/src/utils/form_fields.ts
index 4c9b849..c4e6eec 100644
--- a/frontend/src/utils/form_fields.ts
+++ b/frontend/src/utils/form_fields.ts
@@ -15,6 +15,7 @@ import {
import { Component } from "vue";
import Attachment from "@/components/fields/Attachment.vue";
import Phone from "@/components/fields/Phone.vue";
+import Table from "@/components/fields/Table.vue";
export type FormFieldType = {
component: Component;
@@ -144,6 +145,18 @@ export const PhoneField: FormFieldType = {
},
};
+export const TableField: FormFieldType = {
+ component: Table,
+ props: {
+ options: {
+ emptyState: {
+ title: "This is a table field",
+ description: "Use this field to input a list of items.",
+ },
+ },
+ },
+};
+
export const formFields: FormFields[] = [
{ name: "Attach", ...AttachmentField },
{ name: "Data", ...DataField },
@@ -162,6 +175,7 @@ export const formFields: FormFields[] = [
{ name: "Text Editor", ...TextEditorField },
{ name: "Checkbox", ...CheckboxField },
{ name: "Phone", ...PhoneField },
+ { name: "Table", ...TableField },
];
export const mapDoctypeFieldForForm = (fieldtype: string): string => {
diff --git a/frontend/src/utils/selectOptions.ts b/frontend/src/utils/selectOptions.ts
new file mode 100644
index 0000000..a82c4f8
--- /dev/null
+++ b/frontend/src/utils/selectOptions.ts
@@ -0,0 +1,71 @@
+import { createResource } from "frappe-ui";
+import type { Ref, ComputedRef } from "vue";
+import { ref, watch } from "vue";
+
+export type SelectOption = {
+ label: string;
+ value: string;
+};
+
+export type FieldForOptions = {
+ fieldtype?: string;
+ options?: string;
+ [key: string]: unknown;
+};
+
+/**
+ * Resolves options for Select and Link form fields.
+ * Returns string[] for Select (newline-separated options), API result for Link, or raw options otherwise.
+ */
+export async function getFieldOptions(
+ field: FieldForOptions
+): Promise {
+ if (!field?.options) {
+ return "";
+ }
+
+ if (field.fieldtype === "Select") {
+ return field.options.split("\n");
+ }
+
+ if (field.fieldtype === "Link") {
+ const resource = createResource({
+ url: "forms_pro.api.form.get_link_field_options",
+ makeParams: () => ({
+ doctype: field.options,
+ filters: {},
+ page_length: 999,
+ }),
+ });
+ await resource.fetch();
+ return resource.data as string[] | SelectOption[];
+ }
+
+ return field.options;
+}
+
+/**
+ * Composable to load and reactively track options for a Select/Link field.
+ * Call load() or rely on automatic load on mount and when field type/options change.
+ */
+export function useFieldOptions(
+ field: Ref | ComputedRef
+) {
+ const options = ref(null);
+
+ const load = async () => {
+ const result = await getFieldOptions(field.value);
+ options.value =
+ result === "" || result === undefined
+ ? null
+ : (result as string[] | SelectOption[]);
+ };
+
+ watch(
+ () => [field.value.fieldtype, field.value.options],
+ () => load(),
+ { immediate: true }
+ );
+
+ return { options, load };
+}