diff --git a/.gitignore b/.gitignore index c65d3262..19cd8f63 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,9 @@ # Ignore .devcontainer files compose-dev.yaml +# Ignore asdf ruby version file +.tool-versions + # Ignore GCP keyfile gcp-storage-keyfile.json diff --git a/app/controllers/imports_controller.rb b/app/controllers/imports_controller.rb index 814ebf13..0857e278 100644 --- a/app/controllers/imports_controller.rb +++ b/app/controllers/imports_controller.rb @@ -38,6 +38,21 @@ class ImportsController < ApplicationController def load end + def upload_csv + begin + @import.raw_csv_str = import_params[:raw_csv_str].read + rescue NoMethodError + flash.now[:error] = "Please select a file to upload" + render :load, status: :unprocessable_entity and return + end + if @import.save + redirect_to configure_import_path(@import), notice: t(".import_loaded") + else + flash.now[:error] = @import.errors.full_messages.to_sentence + render :load, status: :unprocessable_entity + end + end + def load_csv if @import.update(import_params) redirect_to configure_import_path(@import), notice: t(".import_loaded") diff --git a/app/javascript/controllers/csv_upload_controller.js b/app/javascript/controllers/csv_upload_controller.js new file mode 100644 index 00000000..af133e61 --- /dev/null +++ b/app/javascript/controllers/csv_upload_controller.js @@ -0,0 +1,94 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["input", "preview", "submit", "filename", "filesize"] + + connect() { + this.submitTarget.disabled = true + } + + addFile(event) { + const file = event.target.files[0] + this._fileAdded(file) + } + + dragover(event) { + event.preventDefault() + event.stopPropagation() + event.currentTarget.classList.add("bg-gray-100") + } + + dragleave(event) { + event.preventDefault() + event.stopPropagation() + event.currentTarget.classList.remove("bg-gray-100") + } + + drop(event) { + event.preventDefault() + event.stopPropagation() + event.currentTarget.classList.remove("bg-gray-100") + + const file = event.dataTransfer.files[0] + if (file && this._isCSVFile(file)) { + this._setFileInput(file); + this._fileAdded(file) + } else { + this.previewTarget.classList.add("text-red-500") + this.previewTarget.textContent = "Only CSV files are allowed." + } + } + + // Private + + _fetchFileSize(size) { + let fileSize = ''; + if (size < 1024 * 1024) { + fileSize = (size / 1024).toFixed(2) + ' KB'; // Convert bytes to KB + } else { + fileSize = (size / (1024 * 1024)).toFixed(2) + ' MB'; // Convert bytes to MB + } + return fileSize; + } + + _fileAdded(file) { + const fileSizeLimit = 5 * 1024 * 1024 // 5MB + + if (file) { + if (file.size > fileSizeLimit) { + this.previewTarget.classList.add("text-red-500") + this.previewTarget.textContent = "File size exceeds the limit of 5MB" + return + } + + this.submitTarget.classList.remove([ + "bg-alpha-black-25", + "text-gray", + "cursor-not-allowed", + ]); + this.submitTarget.classList.add( + "bg-gray-900", + "text-white", + "cursor-pointer", + ); + this.submitTarget.disabled = false; + this.previewTarget.innerHTML = document.querySelector("#template-preview").innerHTML; + this.previewTarget.classList.remove("text-red-500") + this.previewTarget.classList.add("text-gray-900") + this.filenameTarget.textContent = file.name; + this.filesizeTarget.textContent = this._fetchFileSize(file.size); + } + } + + _isCSVFile(file) { + const acceptedTypes = ["text/csv", "application/csv", ".csv"] + const extension = file.name.split('.').pop().toLowerCase() + return acceptedTypes.includes(file.type) || extension === "csv" + } + + _setFileInput(file) { + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(file); + this.inputTarget.files = dataTransfer.files; + } +} diff --git a/app/views/imports/_csv_paste.html.erb b/app/views/imports/_csv_paste.html.erb new file mode 100644 index 00000000..6006e874 --- /dev/null +++ b/app/views/imports/_csv_paste.html.erb @@ -0,0 +1,27 @@ +<%= form_with model: @import, url: load_import_path(@import) do |form| %> +
<%= t(".instructions") %>
++ <%= t(".instructions") %> + + <%= link_to "download this template", "/transactions.csv", download: "" %> + +
+<%= t(".description") %>
- <%= form_with model: @import, url: load_import_path(@import) do |form| %> -<%= t(".instructions") %>
+