log editing
This commit is contained in:
parent
46072422d4
commit
642a9b362c
@ -3,10 +3,11 @@ class LogsController < ApplicationController
|
|||||||
before_action :ensure_valid_user
|
before_action :ensure_valid_user
|
||||||
|
|
||||||
before_action :set_log, only: [:show, :update, :destroy]
|
before_action :set_log, only: [:show, :update, :destroy]
|
||||||
|
before_action :set_recipe, only: [:new, :create]
|
||||||
before_action :require_recipe, only: [:new, :create]
|
before_action :require_recipe, only: [:new, :create]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@logs = Log.for_user(current_user).order(:date).page(params[:page]).per(params[:per])
|
@logs = Log.for_user(current_user).order(date: :desc).page(params[:page]).per(params[:per])
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
41
app/javascript/components/AppDatePicker.vue
Normal file
41
app/javascript/components/AppDatePicker.vue
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<app-text-field :value="stringValue" @input="input" :label="label" type="date"></app-text-field>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import DateTimeUtils from "../lib/DateTimeUtils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
required: false,
|
||||||
|
type: Date
|
||||||
|
},
|
||||||
|
|
||||||
|
label: {
|
||||||
|
required: false,
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
stringValue() {
|
||||||
|
return DateTimeUtils.formatDateForEdit(this.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
input(val) {
|
||||||
|
let d = DateTimeUtils.toDate(val + " 00:00");
|
||||||
|
this.$emit("input", d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import DateTimeUtils from "../lib/DateTimeUtils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
dateTime: {
|
dateTime: {
|
||||||
@ -26,64 +28,22 @@
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
dateObj() {
|
dateObj() {
|
||||||
if (this.dateTime instanceof Date) {
|
return DateTimeUtils.toDate(this.dateTime);
|
||||||
return this.dateTime;
|
|
||||||
} else if (this.dateTime === null || this.dateTime.length === 0) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return new Date(this.dateTime);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
friendlyString() {
|
friendlyString() {
|
||||||
const parts = [];
|
const parts = [];
|
||||||
if (this.showDate) {
|
if (this.showDate) {
|
||||||
parts.push(this.formatDate());
|
parts.push(DateTimeUtils.formatDate(this.dateObj));
|
||||||
}
|
}
|
||||||
if (this.showTime) {
|
if (this.showTime) {
|
||||||
parts.push(this.formatTime(true));
|
parts.push(DateTimeUtils.formatTime(this.dateObj, true));
|
||||||
}
|
}
|
||||||
return parts.join(" ");
|
return parts.join(" ");
|
||||||
},
|
},
|
||||||
|
|
||||||
fullString() {
|
fullString() {
|
||||||
return this.formatDate() + " " + this.formatTime(false);
|
return DateTimeUtils.formatTimestamp(this.dateObj);
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
formatDate() {
|
|
||||||
if (this.dateObj) {
|
|
||||||
return [this.dateObj.getMonth() + 1, this.dateObj.getDate(), this.dateObj.getFullYear() % 100].join("/");
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
formatTime(use12hour) {
|
|
||||||
if (this.dateObj) {
|
|
||||||
let h = this.dateObj.getHours();
|
|
||||||
const m = this.dateObj.getMinutes().toString().padStart(2, "0");
|
|
||||||
let meridiem = "";
|
|
||||||
|
|
||||||
if (use12hour) {
|
|
||||||
meridiem = " am";
|
|
||||||
|
|
||||||
if (h === 0) {
|
|
||||||
h = 12;
|
|
||||||
} else if (h > 12) {
|
|
||||||
h = h - 12;
|
|
||||||
meridiem = " pm";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
h = h.toString().padStart(2, "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
return h + ":" + m + meridiem;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,20 @@
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 5
|
default: 5
|
||||||
},
|
},
|
||||||
rating: {
|
|
||||||
|
readonly: {
|
||||||
|
required: false,
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
|
||||||
|
step: {
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
default: 0.5
|
||||||
|
},
|
||||||
|
|
||||||
|
value: {
|
||||||
required: false,
|
required: false,
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0
|
default: 0
|
||||||
@ -28,17 +41,25 @@
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
temporaryWidth: null
|
temporaryValue: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
ratingPercent() {
|
ratingPercent() {
|
||||||
return (this.rating / this.starCount) * 100.0;
|
return ((this.value || 0) / this.starCount) * 100.0;
|
||||||
|
},
|
||||||
|
|
||||||
|
temporaryPercent() {
|
||||||
|
if (this.temporaryValue !== null) {
|
||||||
|
return (this.temporaryValue / this.starCount) * 100.0;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
filledStyle() {
|
filledStyle() {
|
||||||
const width = this.temporaryWidth === null ? this.ratingPercent : this.temporaryWidth;
|
const width = this.temporaryPercent || this.ratingPercent;
|
||||||
return {
|
return {
|
||||||
width: width + "%"
|
width: width + "%"
|
||||||
};
|
};
|
||||||
@ -47,21 +68,32 @@
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
handleClick(evt) {
|
handleClick(evt) {
|
||||||
console.log("click");
|
if (this.temporaryValue !== null) {
|
||||||
|
this.$emit("input", this.temporaryValue);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleMousemove(evt) {
|
handleMousemove(evt) {
|
||||||
|
if (this.readonly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const wrapperBox = this.$refs.wrapper.getBoundingClientRect();
|
const wrapperBox = this.$refs.wrapper.getBoundingClientRect();
|
||||||
const wrapperWidth = wrapperBox.right - wrapperBox.left;
|
const wrapperWidth = wrapperBox.right - wrapperBox.left;
|
||||||
const mousePosition = evt.clientX;
|
const mousePosition = evt.clientX;
|
||||||
|
|
||||||
if (mousePosition > wrapperBox.left && mousePosition < wrapperBox.right) {
|
if (mousePosition > wrapperBox.left && mousePosition < wrapperBox.right) {
|
||||||
this.temporaryWidth = ((mousePosition - wrapperBox.left) / wrapperWidth) * 100.0;
|
const filledRatio = ((mousePosition - wrapperBox.left) / wrapperWidth);
|
||||||
|
|
||||||
|
const totalSteps = this.starCount / this.step;
|
||||||
|
const filledSteps = Math.round(totalSteps * filledRatio);
|
||||||
|
|
||||||
|
this.temporaryValue = filledSteps * this.step;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleMouseleave(evt) {
|
handleMouseleave(evt) {
|
||||||
this.temporaryWidth = null;
|
this.temporaryValue = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -4,11 +4,24 @@
|
|||||||
|
|
||||||
<h1 class="title">Creating Log for {{ log.recipe.name }}</h1>
|
<h1 class="title">Creating Log for {{ log.recipe.name }}</h1>
|
||||||
|
|
||||||
<p>Edit Rating</p>
|
<div class="columns">
|
||||||
|
<div class="column">
|
||||||
|
<app-date-picker v-model="log.date" label="Date"></app-date-picker>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p>Edit Date</p>
|
<div class="column">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label is-small-mobile">Rating</label>
|
||||||
|
<div class="control">
|
||||||
|
<app-rating v-model="log.rating" :step="1"></app-rating>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<app-text-field label="Notes" :value="log.notes" type="textarea"></app-text-field>
|
<app-text-field label="Notes" v-model="log.notes" type="textarea"></app-text-field>
|
||||||
|
|
||||||
|
<slot></slot>
|
||||||
|
|
||||||
<recipe-edit :recipe="log.recipe" :for-logging="true"></recipe-edit>
|
<recipe-edit :recipe="log.recipe" :for-logging="true"></recipe-edit>
|
||||||
|
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<log-edit v-if="log.recipe !== null" :log="log"></log-edit>
|
<log-edit v-if="log.recipe !== null" :log="log">
|
||||||
|
<div class="buttons">
|
||||||
|
<button type="button" class="button is-primary" @click="save">Save Log</button>
|
||||||
|
<router-link class="button is-secondary" to="/">Cancel</router-link>
|
||||||
|
</div>
|
||||||
|
</log-edit>
|
||||||
|
|
||||||
<button type="button" class="button is-primary" @click="save">Save</button>
|
<div class="buttons">
|
||||||
<router-link class="button is-secondary" to="/">Cancel</router-link>
|
<button type="button" class="button is-primary" @click="save">Save Log</button>
|
||||||
|
<router-link class="button is-secondary" to="/">Cancel</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -37,6 +44,8 @@
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
save() {
|
save() {
|
||||||
|
this.log.original_recipe_id = this.recipeId;
|
||||||
|
|
||||||
this.loadResource(
|
this.loadResource(
|
||||||
api.postLog(this.log)
|
api.postLog(this.log)
|
||||||
.then(() => this.$router.push('/'))
|
.then(() => this.$router.push('/'))
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
|
|
||||||
<tr v-for="l in logs" :key="l.id">
|
<tr v-for="l in logs" :key="l.id">
|
||||||
<td>{{l.recipe.name}}</td>
|
<td>{{l.recipe.name}}</td>
|
||||||
<td>l.date</td>
|
<td><app-date-time :date-time="l.date" :show-time="false"></app-date-time> </td>
|
||||||
<td>l.rating</td>
|
<td><app-rating :value="l.rating" readonly></app-rating></td>
|
||||||
<td>l.notes</td>
|
<td>{{l.notes}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<app-rating v-if="r.rating !== null" :rating="r.rating" ></app-rating>
|
<app-rating v-if="r.rating !== null" :value="r.rating" readonly></app-rating>
|
||||||
<span v-else>--</span>
|
<span v-else>--</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ r.yields }}</td>
|
<td>{{ r.yields }}</td>
|
||||||
|
@ -283,19 +283,25 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildLogParams(log) {
|
buildLogParams(log) {
|
||||||
|
const recParams = this.buildRecipeParams(log.recipe);
|
||||||
|
|
||||||
|
if (recParams.recipe && recParams.recipe.recipe_ingredients_attributes) {
|
||||||
|
recParams.recipe.recipe_ingredients_attributes.forEach(ri => ri.id = null);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
log: {
|
log: {
|
||||||
date: log.date,
|
date: log.date,
|
||||||
rating: log.rating,
|
rating: log.rating,
|
||||||
notes: log.notes,
|
notes: log.notes,
|
||||||
source_recipe_id: log.source_recipe_id,
|
source_recipe_id: log.source_recipe_id,
|
||||||
recipe_attributes: this.buildRecipeParams(log.recipe)
|
recipe_attributes: recParams.recipe
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
postLog(log) {
|
postLog(log) {
|
||||||
return this.post("/logs/", this.buildLogParams(log));
|
return this.post("/recipes/" + log.original_recipe_id + "/logs/", this.buildLogParams(log));
|
||||||
}
|
}
|
||||||
|
|
||||||
patchLog(log) {
|
patchLog(log) {
|
||||||
|
73
app/javascript/lib/DateTimeUtils.js
Normal file
73
app/javascript/lib/DateTimeUtils.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
|
||||||
|
function zeroPad(val, length) {
|
||||||
|
return val.toString().padStart(length, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the given date is a Date object.
|
||||||
|
function toDate(date) {
|
||||||
|
if (date instanceof Date) {
|
||||||
|
return date;
|
||||||
|
} else if (date === null || date.length === 0) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return new Date(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(dateObj) {
|
||||||
|
if (dateObj) {
|
||||||
|
return [dateObj.getMonth() + 1, dateObj.getDate(), dateObj.getFullYear() % 100].join("/");
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateForEdit(dateObj) {
|
||||||
|
if (dateObj) {
|
||||||
|
return [dateObj.getFullYear(), zeroPad(dateObj.getMonth() + 1, 2), zeroPad(dateObj.getDate(), 2)].join("-");
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTimestamp(dateObj) {
|
||||||
|
if (dateObj) {
|
||||||
|
return formatDateForEdit(dateObj) + " " + formatTime(dateObj, false);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(dateObj, use12hour) {
|
||||||
|
if (dateObj) {
|
||||||
|
let h = dateObj.getHours();
|
||||||
|
const m = zeroPad(dateObj.getMinutes(), 2);
|
||||||
|
let meridiem = "";
|
||||||
|
|
||||||
|
if (use12hour) {
|
||||||
|
meridiem = " am";
|
||||||
|
|
||||||
|
if (h === 0) {
|
||||||
|
h = 12;
|
||||||
|
} else if (h > 12) {
|
||||||
|
h = h - 12;
|
||||||
|
meridiem = " pm";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h = h.toString().padStart(2, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
return h + ":" + m + meridiem;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
toDate,
|
||||||
|
formatDate,
|
||||||
|
formatDateForEdit,
|
||||||
|
formatTimestamp,
|
||||||
|
formatTime
|
||||||
|
}
|
@ -11,6 +11,7 @@ import App from '../components/App';
|
|||||||
|
|
||||||
import AppAutocomplete from "../components/AppAutocomplete";
|
import AppAutocomplete from "../components/AppAutocomplete";
|
||||||
import AppDateTime from "../components/AppDateTime";
|
import AppDateTime from "../components/AppDateTime";
|
||||||
|
import AppDatePicker from "../components/AppDatePicker";
|
||||||
import AppIcon from "../components/AppIcon";
|
import AppIcon from "../components/AppIcon";
|
||||||
import AppModal from "../components/AppModal";
|
import AppModal from "../components/AppModal";
|
||||||
import AppNavbar from "../components/AppNavbar";
|
import AppNavbar from "../components/AppNavbar";
|
||||||
@ -21,6 +22,7 @@ import AppTextField from "../components/AppTextField";
|
|||||||
|
|
||||||
Vue.component("AppAutocomplete", AppAutocomplete);
|
Vue.component("AppAutocomplete", AppAutocomplete);
|
||||||
Vue.component("AppDateTime", AppDateTime);
|
Vue.component("AppDateTime", AppDateTime);
|
||||||
|
Vue.component("AppDatePicker", AppDatePicker);
|
||||||
Vue.component("AppIcon", AppIcon);
|
Vue.component("AppIcon", AppIcon);
|
||||||
Vue.component("AppModal", AppModal);
|
Vue.component("AppModal", AppModal);
|
||||||
Vue.component("AppNavbar", AppNavbar);
|
Vue.component("AppNavbar", AppNavbar);
|
||||||
|
Loading…
Reference in New Issue
Block a user