Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions forms_pro/forms_pro/doctype/form_field/form_field.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"fieldtype": "Select",
"in_list_view": 1,
"label": "Fieldtype",
"options": "Data\nNumber\nEmail\nDate\nDate Time\nDate Range\nTime Picker\nPassword\nSelect\nSwitch\nTextarea\nText Editor\nLink",
"options": "Attach\nData\nNumber\nEmail\nDate\nDate Time\nDate Range\nTime Picker\nPassword\nSelect\nSwitch\nTextarea\nText Editor\nLink",
"reqd": 1
},
{
Expand Down Expand Up @@ -68,7 +68,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-01-09 13:23:39.055114",
"modified": "2026-01-09 14:57:39.192268",
"modified_by": "Administrator",
"module": "Forms Pro",
"name": "Form Field",
Expand Down
1 change: 1 addition & 0 deletions forms_pro/forms_pro/doctype/form_field/form_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class FormField(Document):
description: DF.SmallText | None
fieldname: DF.Data
fieldtype: DF.Literal[
"Attach",
"Data",
"Number",
"Email",
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/components/builder/FieldRenderer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,29 @@ onMounted(() => {
/>
</div>
</div>
<div v-else-if="fieldData.fieldtype == 'Attach'">
<div class="flex gap-2 items-start">
<input
v-if="inEditMode"
placeholder="Label"
type="text"
v-model="fieldData.label"
class="bg-transparent border-none outline-none text-base focus:ring-0 w-fit px-0 py-1"
/>
<label class="text-base" v-else>{{ fieldData.label }}</label>
<Asterisk v-if="fieldData.reqd" class="w-4 h-4 text-red-400" />
</div>
<RenderField
:value="modelValue"
@update:value="(value: string) => (modelValue = value)"
:field="fieldData"
:class="{ 'pointer-events-none': inEditMode }"
:disabled="disabled"
/>
<small class="text-gray-500">
{{ fieldData.description }}
</small>
</div>
<div v-else :class="getClasses">
<div class="flex gap-2 items-start">
<input
Expand Down
97 changes: 97 additions & 0 deletions frontend/src/components/fields/Attachment.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<script setup lang="ts">
import { ErrorMessage, FileUploader, FormControl } from "frappe-ui";
import { computed, ref } from "vue";
import { FileImage, FileText } from "lucide-vue-next";

const props = defineProps({
field: {
type: Object,
required: true,
},
});

const emit = defineEmits(["update:value"]);

const value = defineModel<string>();

const inPreview = ref(false);

type FileType = {
content_hash: string;
creation: string;
docstatus: number;
doctype: string;
file_name: string;
file_size: number;
file_type: string;
file_url: string;
folder: string;
idx: number;
is_attachments_folder: number;
is_folder: number;
is_home_folder: number;
is_private: number;
modified: string;
modified_by: string;
name: string;
owner: string;
uploaded_to_dropbox: number;
uploaded_to_google_drive: number;
};

const fileData = ref<FileType | null>(null);

const formatFileSize = (fileSize: number) => {
if (fileSize < 1024) return `${fileSize} B`;
if (fileSize < 1024 * 1024) return `${(fileSize / 1024).toFixed(2)} KB`;
if (fileSize < 1024 * 1024 * 1024) return `${(fileSize / 1024 / 1024).toFixed(2)} MB`;
return `${(fileSize / 1024 / 1024 / 1024).toFixed(2)} GB`;
};

const handleChange = (file: FileType) => {
fileData.value = file;
if (file.file_url) {
emit("update:value", file.file_url);
}
inPreview.value = true;
};

const handleRemove = () => {
fileData.value = null;
emit("update:value", "");
inPreview.value = false;
};
</script>
<template>
<div class="flex gap-2 flex-col mt-2">
<FileUploader
v-if="!inPreview"
v-bind="props.field"
:class="{ 'pointer-events-none': props.field.inEditMode }"
:disabled="props.field.disabled"
@success="(file: FileType) => handleChange(file)"
>
<template #default="{ uploading, progress, error, openFileSelector }">
<Button @click="openFileSelector()" :loading="uploading">
{{ uploading ? `Uploading ${progress}%` : "Upload" }}
</Button>
<ErrorMessage :message="error" />
</template>
</FileUploader>
<div v-if="inPreview" class="p-4 bg-white border rounded-md flex flex-col gap-2">
<a :href="fileData?.file_url" target="_blank" class="text-sm font-medium">
{{ fileData?.file_name }}
</a>
<span class="text-sm text-gray-500">
{{ formatFileSize(fileData?.file_size || 0) }}
</span>
<Button
theme="red"
label="Remove"
icon="trash"
tooltip="Remove file"
@click="handleRemove"
/>
</div>
</div>
</template>
1 change: 1 addition & 0 deletions frontend/src/pages/SubmissionPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const submissionFormStore = useSubmissionForm();
submissionFormStore.initialize(route.params.route as string);
</script>
<template>
<pre>{{ submissionFormStore }}</pre>
<div class="p-8 bg-surface-gray-1 min-h-svh space-y-16">
<PageHeader />
<PreviousSubmissionSection v-if="submissionFormStore.userSubmissions" />
Expand Down
12 changes: 11 additions & 1 deletion frontend/src/utils/form_fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
DatePicker,
DateRangePicker,
DateTimePicker,
FileUploader,
Rating,
Select,
Switch,
Expand All @@ -14,6 +13,7 @@ import {
Password,
} from "frappe-ui";
import { Component } from "vue";
import Attachment from "@/components/fields/Attachment.vue";

export type FormFieldType = {
component: Component;
Expand All @@ -25,6 +25,15 @@ export type FormFields = FormFieldType & {
};

// Individual form field components as dictionaries

export const AttachmentField: FormFieldType = {
component: Attachment,
props: {
variant: "outline",
filetypes: ["image/*", ".jpg", ".gif", ".pdf"],
},
};

export const DataField: FormFieldType = {
component: FormControl,
props: { type: "text", variant: "outline" },
Expand Down Expand Up @@ -128,6 +137,7 @@ export const CheckboxField: FormFieldType = {
};

export const formFields: FormFields[] = [
{ name: "Attach", ...AttachmentField },
{ name: "Data", ...DataField },
{ name: "Link", ...SelectField },
{ name: "Number", ...NumberField },
Expand Down