logs
This commit is contained in:
parent
2da16e334e
commit
46072422d4
@ -2,12 +2,11 @@ class LogsController < ApplicationController
|
||||
|
||||
before_action :ensure_valid_user
|
||||
|
||||
before_action :set_log, only: [:show, :edit, :update, :destroy]
|
||||
before_action :set_recipe, only: [:new, :create]
|
||||
before_action :set_log, only: [:show, :update, :destroy]
|
||||
before_action :require_recipe, only: [:new, :create]
|
||||
|
||||
def index
|
||||
@logs = Log.for_user(current_user).order(:date)
|
||||
@logs = Log.for_user(current_user).order(:date).page(params[:page]).per(params[:per])
|
||||
end
|
||||
|
||||
def show
|
||||
@ -45,9 +44,9 @@ class LogsController < ApplicationController
|
||||
@log.source_recipe = @recipe
|
||||
|
||||
if @log.save
|
||||
redirect_to logs_path, notice: 'Log Entry was successfully created.'
|
||||
render json: { success: true }
|
||||
else
|
||||
render :new
|
||||
render json: @log.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -79,7 +79,7 @@
|
||||
h = h.toString().padStart(2, "0");
|
||||
}
|
||||
|
||||
return h + ":" + m + " " + meridiem;
|
||||
return h + ":" + m + meridiem;
|
||||
|
||||
} else {
|
||||
return "";
|
||||
|
@ -16,8 +16,9 @@
|
||||
<router-link to="/" class="navbar-item">Recipes</router-link>
|
||||
<router-link to="/ingredients" class="navbar-item">Ingredients</router-link>
|
||||
<router-link to="/calculator" class="navbar-item">Calculator</router-link>
|
||||
<router-link to="/about" class="navbar-item">About</router-link>
|
||||
<router-link v-if="isLoggedIn" to="/logs" class="navbar-item">Log</router-link>
|
||||
<router-link v-if="isLoggedIn" to="/notes" class="navbar-item">Notes</router-link>
|
||||
<router-link to="/about" class="navbar-item">About</router-link>
|
||||
<a v-if="isAdmin" class="navbar-item" href="/admin/users">Admin</a>
|
||||
|
||||
</div>
|
||||
|
48
app/javascript/components/AppTextField.vue
Normal file
48
app/javascript/components/AppTextField.vue
Normal file
@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="field">
|
||||
<label v-if="label.length" class="label is-small-mobile">{{ label }}</label>
|
||||
<div class="control">
|
||||
<textarea v-if="isTextarea" class="textarea is-small-mobile" :value="value" @input="input"></textarea>
|
||||
<input v-else :type="type" class="input is-small-mobile" :value="value" @input="input">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
label: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
value: {
|
||||
required: false,
|
||||
type: [String, Number],
|
||||
default: ""
|
||||
},
|
||||
type: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: "text"
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
isTextarea() {
|
||||
return this.type === "textarea";
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
input(evt) {
|
||||
this.$emit("input", evt.target.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
39
app/javascript/components/LogEdit.vue
Normal file
39
app/javascript/components/LogEdit.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<template>
|
||||
|
||||
<div>
|
||||
|
||||
<h1 class="title">Creating Log for {{ log.recipe.name }}</h1>
|
||||
|
||||
<p>Edit Rating</p>
|
||||
|
||||
<p>Edit Date</p>
|
||||
|
||||
<app-text-field label="Notes" :value="log.notes" type="textarea"></app-text-field>
|
||||
|
||||
<recipe-edit :recipe="log.recipe" :for-logging="true"></recipe-edit>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import RecipeEdit from "./RecipeEdit";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
log: {
|
||||
required: true,
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
RecipeEdit
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
14
app/javascript/components/LogShow.vue
Normal file
14
app/javascript/components/LogShow.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
@ -1,65 +1,37 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="title">{{ action }} {{ recipe.name || "[Unamed Recipe]" }}</h1>
|
||||
<template v-if="!forLogging">
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="field">
|
||||
<label class="label is-small-mobile">Name</label>
|
||||
<div class="control">
|
||||
<input class="input is-small-mobile" type="text" placeholder="name" v-model="recipe.name">
|
||||
</div>
|
||||
</div>
|
||||
<app-text-field label="Name" v-model="recipe.name"></app-text-field>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="field">
|
||||
<label class="label is-small-mobile">Source</label>
|
||||
<div class="control">
|
||||
<input class="input is-small-mobile" type="text" placeholder="source" v-model="recipe.source">
|
||||
</div>
|
||||
</div>
|
||||
<app-text-field label="Source" v-model="recipe.source"></app-text-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label is-small-mobile">Description</label>
|
||||
<div class="control">
|
||||
<textarea class="textarea is-small-mobile" placeholder="description" v-model="recipe.description"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<app-text-field label="Description" type="textarea" v-model="recipe.description"></app-text-field>
|
||||
|
||||
<div class="field">
|
||||
<label class="label is-small-mobile">Tags</label>
|
||||
<app-tag-editor v-model="recipe.tags"></app-tag-editor>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="field">
|
||||
<label class="label is-small-mobile">Yields</label>
|
||||
<div class="control">
|
||||
<input class="input is-small-mobile" type="text" placeholder="servings" v-model="recipe.yields">
|
||||
</div>
|
||||
</div>
|
||||
<app-text-field label="Yields" v-model="recipe.yields"></app-text-field>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="field">
|
||||
<label class="label is-small-mobile">Total Time</label>
|
||||
<div class="control">
|
||||
<input class="input is-small-mobile" type="number" placeholder="minutes" v-model="recipe.total_time">
|
||||
</div>
|
||||
</div>
|
||||
<app-text-field label="Total Time" type="number" v-model="recipe.total_time"></app-text-field>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="field">
|
||||
<label class="label is-small-mobile">Active Time</label>
|
||||
<div class="control">
|
||||
<input class="input is-small-mobile" type="number" placeholder="minutes" v-model="recipe.active_time">
|
||||
</div>
|
||||
</div>
|
||||
<app-text-field label="Active Time" v-model="recipe.active_time"></app-text-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -98,10 +70,10 @@
|
||||
required: true,
|
||||
type: Object
|
||||
},
|
||||
action: {
|
||||
forLogging: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: "Editing"
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
|
14
app/javascript/components/TheLog.vue
Normal file
14
app/javascript/components/TheLog.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
63
app/javascript/components/TheLogCreator.vue
Normal file
63
app/javascript/components/TheLogCreator.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<log-edit v-if="log.recipe !== null" :log="log"></log-edit>
|
||||
|
||||
<button type="button" class="button is-primary" @click="save">Save</button>
|
||||
<router-link class="button is-secondary" to="/">Cancel</router-link>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import LogEdit from "./LogEdit";
|
||||
import { mapState } from "vuex";
|
||||
import api from "../lib/Api";
|
||||
import * as Errors from '../lib/Errors';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
validationErrors: [],
|
||||
log: {
|
||||
date: null,
|
||||
rating: null,
|
||||
notes: null,
|
||||
recipe: null
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
recipeId: state => state.route.params.recipeId,
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
save() {
|
||||
this.loadResource(
|
||||
api.postLog(this.log)
|
||||
.then(() => this.$router.push('/'))
|
||||
.catch(Errors.onlyFor(Errors.ApiValidationError, err => this.validationErrors = err.validationErrors()))
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.loadResource(
|
||||
api.getRecipe(this.recipeId)
|
||||
.then(data => { this.log.recipe = data; return data; })
|
||||
);
|
||||
},
|
||||
|
||||
components: {
|
||||
LogEdit
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
14
app/javascript/components/TheLogEditor.vue
Normal file
14
app/javascript/components/TheLogEditor.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
93
app/javascript/components/TheLogList.vue
Normal file
93
app/javascript/components/TheLogList.vue
Normal file
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="title">
|
||||
Log Entries
|
||||
</h1>
|
||||
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Recipe</th>
|
||||
<th>Date</th>
|
||||
<th>Rating</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="l in logs" :key="l.id">
|
||||
<td>{{l.recipe.name}}</td>
|
||||
<td>l.date</td>
|
||||
<td>l.rating</td>
|
||||
<td>l.notes</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import api from "../lib/Api";
|
||||
import debounce from "lodash/debounce";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
logData: null,
|
||||
search: {
|
||||
page: 1,
|
||||
per: 25
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
logs() {
|
||||
if (this.logData) {
|
||||
return this.logData.logs;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
totalPages() {
|
||||
if (this.logData) {
|
||||
return this.logData.total_pages
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
|
||||
currentPage() {
|
||||
if (this.logData) {
|
||||
return this.logData.current_page
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
changePage(idx) {
|
||||
this.search.page = idx;
|
||||
},
|
||||
|
||||
getList: debounce(function() {
|
||||
this.loadResource(
|
||||
api.getLogList(this.search.page, this.search.per)
|
||||
.then(data => this.logData = data)
|
||||
);
|
||||
}, 500, {leading: true, trailing: true})
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$watch("search",
|
||||
() => this.getList(),
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<h1 class="title">Creating {{ recipe.name || "[Unamed Recipe]" }}</h1>
|
||||
|
||||
<recipe-edit :recipe="recipe" action="Creating"></recipe-edit>
|
||||
|
||||
<button type="button" class="button is-primary" @click="save">Save</button>
|
||||
@ -12,13 +14,13 @@
|
||||
<script>
|
||||
|
||||
import RecipeEdit from "./RecipeEdit";
|
||||
import { mapState } from "vuex";
|
||||
import api from "../lib/Api";
|
||||
import * as Errors from '../lib/Errors';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
validationErrors: [],
|
||||
recipe: {
|
||||
name: null,
|
||||
source: null,
|
||||
@ -33,12 +35,6 @@
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
recipeId: state => state.route.params.id,
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
save() {
|
||||
this.loadResource(
|
||||
|
@ -5,6 +5,7 @@
|
||||
Loading...
|
||||
</div>
|
||||
<div v-else>
|
||||
<h1 class="title">Editing {{ recipe.name || "[Unamed Recipe]" }}</h1>
|
||||
<recipe-edit :recipe="recipe"></recipe-edit>
|
||||
</div>
|
||||
|
||||
|
@ -52,6 +52,9 @@
|
||||
<td>{{ formatRecipeTime(r.total_time, r.active_time) }}</td>
|
||||
<td><app-date-time :date-time="r.created_at" :show-time="false"></app-date-time></td>
|
||||
<td>
|
||||
<router-link v-if="isLoggedIn" :to="{name: 'new_log', params: { recipeId: r.id } }" class="button is-primary">
|
||||
<app-icon icon="star" size="md"></app-icon>
|
||||
</router-link>
|
||||
<router-link v-if="isLoggedIn" :to="{name: 'edit_recipe', params: { id: r.id } }" class="button is-primary">
|
||||
<app-icon icon="pencil" size="md"></app-icon>
|
||||
</router-link>
|
||||
|
@ -273,6 +273,35 @@ class Api {
|
||||
return this.del("/notes/" + note.id);
|
||||
}
|
||||
|
||||
getLogList(page, per) {
|
||||
const params = {
|
||||
page,
|
||||
per
|
||||
};
|
||||
|
||||
return this.get("/logs", params);
|
||||
}
|
||||
|
||||
buildLogParams(log) {
|
||||
return {
|
||||
log: {
|
||||
date: log.date,
|
||||
rating: log.rating,
|
||||
notes: log.notes,
|
||||
source_recipe_id: log.source_recipe_id,
|
||||
recipe_attributes: this.buildRecipeParams(log.recipe)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
postLog(log) {
|
||||
return this.post("/logs/", this.buildLogParams(log));
|
||||
}
|
||||
|
||||
patchLog(log) {
|
||||
return this.patch("/logs/" + log.id, this.buildLogParams(log));
|
||||
}
|
||||
|
||||
postLogin(username, password) {
|
||||
const params = {
|
||||
username: username,
|
||||
|
@ -17,6 +17,7 @@ import AppNavbar from "../components/AppNavbar";
|
||||
import AppPager from "../components/AppPager";
|
||||
import AppRating from "../components/AppRating";
|
||||
import AppTagEditor from "../components/AppTagEditor";
|
||||
import AppTextField from "../components/AppTextField";
|
||||
|
||||
Vue.component("AppAutocomplete", AppAutocomplete);
|
||||
Vue.component("AppDateTime", AppDateTime);
|
||||
@ -26,6 +27,7 @@ Vue.component("AppNavbar", AppNavbar);
|
||||
Vue.component("AppPager", AppPager);
|
||||
Vue.component("AppRating", AppRating);
|
||||
Vue.component("AppTagEditor", AppTagEditor);
|
||||
Vue.component("AppTextField", AppTextField);
|
||||
|
||||
|
||||
Vue.use(VueProgressBar, {
|
||||
|
@ -4,6 +4,12 @@ import Router from 'vue-router';
|
||||
import The404Page from './components/The404Page';
|
||||
import TheAboutPage from './components/TheAboutPage';
|
||||
import TheCalculator from './components/TheCalculator';
|
||||
|
||||
import TheLog from './components/TheLog';
|
||||
import TheLogList from './components/TheLogList';
|
||||
import TheLogCreator from './components/TheLogCreator';
|
||||
import TheLogEditor from './components/TheLogEditor';
|
||||
|
||||
import TheIngredientList from './components/TheIngredientList';
|
||||
import TheIngredient from "./components/TheIngredient";
|
||||
import TheIngredientEditor from "./components/TheIngredientEditor";
|
||||
@ -76,6 +82,26 @@ router.addRoutes(
|
||||
name: "ingredient",
|
||||
component: TheIngredient
|
||||
},
|
||||
{
|
||||
path: "/logs",
|
||||
name: "logs",
|
||||
component: TheLogList
|
||||
},
|
||||
{
|
||||
path: "/recipes/:recipeId/logs/new",
|
||||
name: "new_log",
|
||||
component: TheLogCreator
|
||||
},
|
||||
{
|
||||
path: "/logs/:id/edit",
|
||||
name: "edit_log",
|
||||
component: TheLogEditor
|
||||
},
|
||||
{
|
||||
path: "/logs/:id",
|
||||
name: "log",
|
||||
component: TheLog
|
||||
},
|
||||
{
|
||||
path: "/notes",
|
||||
name: "notes",
|
||||
|
16
app/views/logs/index.json.jbuilder
Normal file
16
app/views/logs/index.json.jbuilder
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
json.cache_root! [Log.all, @logs] do
|
||||
|
||||
json.extract! @logs, :total_count, :total_pages, :current_page
|
||||
json.page_size @logs.limit_value
|
||||
|
||||
json.logs @logs do |l|
|
||||
json.extract! l, :id, :date, :rating, :notes
|
||||
|
||||
json.recipe do
|
||||
json.id l.recipe.id
|
||||
json.name l.recipe.name
|
||||
end
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue
Block a user