log editing

This commit is contained in:
Dan Elbert 2018-04-13 23:32:34 -05:00
parent 46072422d4
commit 642a9b362c
11 changed files with 203 additions and 66 deletions

View File

@ -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

View 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>

View File

@ -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 "";
}
} }
} }
} }

View File

@ -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;
} }
}, },

View File

@ -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>

View File

@ -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</button> <button type="button" class="button is-primary" @click="save">Save Log</button>
<router-link class="button is-secondary" to="/">Cancel</router-link> <router-link class="button is-secondary" to="/">Cancel</router-link>
</div>
</log-edit>
<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>
</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('/'))

View File

@ -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>

View File

@ -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>

View File

@ -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) {

View 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
}

View File

@ -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);