Skip to content
Snippets Groups Projects
Commit 3e38101a authored by Erik Borgeteien Hansen's avatar Erik Borgeteien Hansen
Browse files

Merge branch 'edit-listing' into 'main'

Edit listing

See merge request !133
parents 89521694 ddf35b37
No related branches found
No related tags found
1 merge request!133Edit listing
Pipeline #181755 failed
<template>
<div
class="md:ring-1 ring-gray-300 rounded-xl overflow-hidden mx-auto mb-auto max-w-md w-full p-4"
>
<!-- Component heading -->
<h3 class="text-xl font-medium text-center text-primary-light mt-4 mb-8">
Rediger gjenstand
</h3>
<!-- Title -->
<div class="mb-6" :class="{ error: v$.updatedItem.title.$errors.length }">
<label
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
id="titleLabel"
>Tittel</label
>
<input
type="text"
id="title"
class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light"
v-model="v$.updatedItem.title.$model"
required
/>
<!-- error message for title-->
<div
class="text-error-medium"
v-for="(error, index) of v$.updatedItem.title.$errors"
:key="index"
>
<div class="text-error-medium text-sm">
{{ error.$message }}
</div>
</div>
</div>
<!-- Category -->
<div class="mb-6">
<label
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400"
id="selectCategoryLabel"
>Kategori</label
>
<select
@change="onChangeCategory($event)"
v-model="v$.updatedItem.selectedCategory.$model"
id="categories"
class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light"
>
<option class="text-gray-400" value="" disabled>
Velg en kategori
</option>
<option
:value="category"
:selected="category == updatedItem.selectedCategory"
v-for="category in categories"
:key="category"
class="text-gray-900 text-sm"
>
{{ category }}
</option>
</select>
<!-- error message for select box -->
<div
class="text-error-medium"
v-for="(error, index) of v$.updatedItem.selectedCategory.$errors"
:key="index"
>
<div class="text-error-medium text-sm">
{{ error.$message }}
</div>
</div>
</div>
<!-- Grupper -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-900 dark:text-gray-400"
>Grupper</label
>
<div
class="overflow-auto w-full max-h-32 mt-2 text-base list-none bg-white rounded divide-y divide-gray-100 dark:bg-gray-700"
>
<ul class="py-1" aria-labelledby="dropdownDefault">
<li>
<div
class="form-check"
v-for="community in communities"
:key="community"
>
<input
class="form-check-input appearance-none h-4 w-4 border border-gray-300 rounded-sm bg-white checked:bg-primary-medium focus:outline-none transition duration-200 mt-1 align-top bg-no-repeat bg-center bg-contain float-left mr-2 cursor-pointer"
type="checkbox"
:checked="isInSelectedCommunity(community.communityId)"
:value="community.communityId"
@change="onChangeCommunity($event)"
/>
<label class="form-check-label inline-block text-gray-800">
{{ community.name }}
</label>
</div>
</li>
</ul>
</div>
<label class="text-error-medium text-sm block">{{
communityErrorMessage
}}</label>
</div>
<!-- price -->
<div
class="mb-6 mt-4"
:class="{ error: v$.updatedItem.price.$errors.length }"
>
<label
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
id="priceLabel"
>Pris</label
>
<input
type="number"
v-model="v$.updatedItem.price.$model"
id="price"
class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light"
required
/>
<!-- error message for price -->
<div
class="text-error"
v-for="(error, index) of v$.updatedItem.price.$errors"
:key="index"
>
<div class="text-error-medium text-sm">
{{ error.$message }}
</div>
</div>
</div>
<!-- Description -->
<div
class="mb-6"
:class="{ error: v$.updatedItem.description.$errors.length }"
>
<label
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400"
id="descriptionLabel"
>Beskrivelse</label
>
<textarea
id="description"
rows="4"
v-model="v$.updatedItem.description.$model"
class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light"
required
></textarea>
<!-- error message for description -->
<div
class="text-error"
v-for="(error, index) of v$.updatedItem.description.$errors"
:key="index"
>
<div class="text-error-medium text-sm">
{{ error.$message }}
</div>
</div>
</div>
<!-- Address -->
<div class="mb-6" :class="{ error: v$.updatedItem.address.$errors.length }">
<label
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
id="addressLabel"
>Adresse</label
>
<input
type="text"
class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light"
v-model="v$.updatedItem.address.$model"
id="adress"
required
/>
<!-- error message for address-->
<div
class="text-error"
v-for="(error, index) of v$.updatedItem.address.$errors"
:key="index"
>
<div class="text-error-medium text-sm">
{{ error.$message }}
</div>
</div>
</div>
<!-- Images -->
<!--
<div>
<label
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400"
id="imageLabel"
>
Legg til flere bilder (bildene må være .png)
</label>
<input
type="file"
ref="file"
style="display: none"
@change="addImage"
multiple
accept="image/png"
/>
<Button :text="'Velg bilde'" @click="$refs.file.click()" />
<div v-for="image in updatedItem.images" :key="image" class="m-2">
<img :src="image" class="w-2/5 inline" alt="Bilde av gjenstanden" />
</div>
</div>
-->
<!-- Save item button -->
<div class="float-right">
<Button :text="'Lagre'" @click="saveClicked" id="saveButton" />
</div>
</div>
</template>
<script>
import useVuelidate from "@vuelidate/core";
import Button from "@/components/BaseComponents/ColoredButton";
import ListingService from "@/services/listing.service";
import CommunityService from "@/services/community.service";
import { parseCurrentUser } from "@/utils/token-utils";
import {
required,
helpers,
maxLength,
between,
minLength,
} from "@vuelidate/validators";
export default {
name: "EditNewItem",
components: {
Button,
},
setup() {
return { v$: useVuelidate() };
},
validations() {
return {
updatedItem: {
title: {
required: helpers.withMessage(
() => "Tittelen kan ikke være tom",
required
),
max: helpers.withMessage(
() => `Tittelen kan inneholde max 50 tegn`,
maxLength(50)
),
},
description: {
required: helpers.withMessage(
() => "Beskrivelsen kan ikke være tom",
required
),
max: helpers.withMessage(
() => `Beskrivelsen kan inneholde max 200 tegn`,
maxLength(200)
),
min: helpers.withMessage(
() => `Beskrivelsen kan ikke være tom`,
minLength(0)
),
},
price: {
required,
between: helpers.withMessage(
() => `Leieprisen kan ikke være større enn 25000`,
between(0, 25000)
),
},
selectedCategory: {
required: helpers.withMessage(() => `Velg en kategori`, required),
},
address: {
required: helpers.withMessage(
() => "Addressen kan ikke være tom",
required
),
max: helpers.withMessage(
() => `Addressen kan inneholde max 50 tegn`,
maxLength(50)
),
},
},
};
},
data() {
return {
updatedItem: {
title: "",
description: "",
address: "",
price: "",
category: "",
selectedCategory: "",
selectedCategories: [],
images: [],
userId: -1,
selectedCommunityId: -1,
selectedCommunities: [],
},
categories: [
"Antikviteter og kunst",
"Dyr og utstyr",
"Elektronikk og hvitevarer",
"Foreldre og barn",
"Fritid, hobby og underholdning",
"Hage, oppussing og hus",
"Klær, kosmetikk og tilbehør",
"Møbler og interiør",
"Næringsvirksomhet",
"Sport og friluftsliv",
"Utstyr til bil, båt og MC",
],
initialItem: {},
communities: [],
communityErrorMessage: "",
images: [],
};
},
methods: {
checkValidation() {
this.v$.updatedItem.$touch();
if (
this.v$.updatedItem.$invalid ||
this.updatedItem.selectedCommunities.length === 0
) {
if (this.updatedItem.selectedCommunities.length === 0) {
this.communityErrorMessage = "Velg gruppe/grupper";
}
return false;
}
return true;
},
async saveClicked() {
if (this.checkValidation()) {
let itemInfo = {
listingID: parseInt(this.initialItem.listingID),
title: this.updatedItem.title,
description: this.updatedItem.description,
pricePerDay: this.updatedItem.price,
address: this.updatedItem.address,
userID: this.updatedItem.userId,
categoryNames: this.updatedItem.selectedCategories,
communityIDs: this.updatedItem.selectedCommunities,
};
await ListingService.putItem(itemInfo);
this.$router.push("/itempage/" + this.initialItem.listingID);
}
},
addImage(event) {
this.updatedItem.images.push(URL.createObjectURL(event.target.files[0]));
},
onChangeCommunity(e) {
this.updatedItem.selectedCommunityId = e.target.value;
let alreadyInGroupList = false;
for (let i = 0; i <= this.updatedItem.selectedCommunities.length; i++) {
if (
this.updatedItem.selectedCommunityId ==
this.updatedItem.selectedCommunities[i]
) {
const index = this.updatedItem.selectedCommunities.indexOf(
this.updatedItem.selectedCommunityId
);
if (index > -1) {
this.updatedItem.selectedCommunities.splice(index, 1);
}
alreadyInGroupList = true;
}
}
if (!alreadyInGroupList) {
this.updatedItem.selectedCommunities.push(
this.updatedItem.selectedCommunityId
);
this.communityErrorMessage = "";
}
},
onChangeCategory(e) {
this.updatedItem.selectedCategory = e.target.value;
this.updatedItem.selectedCategories = [e.target.value];
},
isInSelectedCommunity(id) {
for (let i in this.updatedItem.selectedCommunities) {
if (this.updatedItem.selectedCommunities[i] == id) {
return true;
}
}
return false;
},
},
async beforeMount() {
let itemID = await this.$router.currentRoute.value.params.id;
let item = await ListingService.getItem(itemID);
// Check if user is the owner of the item
let userID = await parseCurrentUser().userId;
if (item.userID == userID) {
this.$router.push(this.$router.options.history.state.back);
}
this.initialItem = item;
this.communities = await CommunityService.getUserCommunities();
this.images = await ListingService.getItemPictures(itemID);
let initialCategories = [];
for (let i in this.initialItem.categoryNames) {
initialCategories.push(this.initialItem.categoryNames[i]);
}
let selectedCategory =
initialCategories.length > 0 ? initialCategories[0] : "";
let initialCommunities = [];
for (let i in this.initialItem.communityIDs) {
initialCommunities.push(this.initialItem.communityIDs[i]);
}
this.updatedItem = {
title: this.initialItem.title,
description: this.initialItem.description,
address: this.initialItem.address,
price: this.initialItem.pricePerDay,
selectedCategories: initialCategories,
selectedCategory: selectedCategory,
images: this.images,
userId: this.initialItem.userID,
selectedCommunityId: 0,
selectedCommunities: initialCommunities,
};
},
};
</script>
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
>Grupper</label >Grupper</label
> >
<div <div
class="overflow-auto w-full h-32 mt-2 text-base list-none bg-white rounded divide-y divide-gray-100 dark:bg-gray-700" class="overflow-auto w-full max-h-32 mt-2 text-base list-none bg-white rounded divide-y divide-gray-100 dark:bg-gray-700"
> >
<ul class="py-1" aria-labelledby="dropdownDefault"> <ul class="py-1" aria-labelledby="dropdownDefault">
<li> <li>
...@@ -203,6 +203,7 @@ ...@@ -203,6 +203,7 @@
<div v-for="image in item.images" :key="image" class="m-2"> <div v-for="image in item.images" :key="image" class="m-2">
<img :src="image" class="w-2/5 inline" alt="Bilde av gjenstanden" /> <img :src="image" class="w-2/5 inline" alt="Bilde av gjenstanden" />
<!-- @click="removeImage(image)" -->
</div> </div>
</div> </div>
...@@ -311,7 +312,19 @@ export default { ...@@ -311,7 +312,19 @@ export default {
imagesToSend: [], imagesToSend: [],
}, },
//Kategorier skal legges inn ved api/hente fra db, her må det endres etterhvert //Kategorier skal legges inn ved api/hente fra db, her må det endres etterhvert
categories: ["Hage", "Kjøkken", "Musikk", "Annet"], categories: [
"Antikviteter og kunst",
"Dyr og utstyr",
"Elektronikk og hvitevarer",
"Foreldre og barn",
"Fritid, hobby og underholdning",
"Hage, oppussing og hus",
"Klær, kosmetikk og tilbehør",
"Møbler og interiør",
"Næringsvirksomhet",
"Sport og friluftsliv",
"Utstyr til bil, båt og MC",
],
groups: [], groups: [],
groupErrorMessage: "", groupErrorMessage: "",
}; };
...@@ -338,7 +351,7 @@ export default { ...@@ -338,7 +351,7 @@ export default {
pricePerDay: this.item.price, pricePerDay: this.item.price,
address: this.item.address, address: this.item.address,
userID: this.item.userId, userID: this.item.userId,
categoryNames: [], categoryNames: [this.item.select],
communityIDs: this.item.selectedGroups, communityIDs: this.item.selectedGroups,
}; };
await postNewItem(itemInfo); await postNewItem(itemInfo);
...@@ -355,8 +368,6 @@ export default { ...@@ -355,8 +368,6 @@ export default {
}, },
addImage: async function (event) { addImage: async function (event) {
this.item.images.push(URL.createObjectURL(event.target.files[0]));
var that = this; var that = this;
let image = event.target.files[0]; let image = event.target.files[0];
let fileReader = new FileReader(); let fileReader = new FileReader();
...@@ -366,6 +377,7 @@ export default { ...@@ -366,6 +377,7 @@ export default {
const API_URL = process.env.VUE_APP_BASEURL; const API_URL = process.env.VUE_APP_BASEURL;
that.item.imagesToSend.push(API_URL + "images/" + id); that.item.imagesToSend.push(API_URL + "images/" + id);
that.item.images.push(API_URL + "images/" + id);
}; };
fileReader.readAsArrayBuffer(image); fileReader.readAsArrayBuffer(image);
}, },
...@@ -393,6 +405,16 @@ export default { ...@@ -393,6 +405,16 @@ export default {
this.groupErrorMessage = ""; this.groupErrorMessage = "";
} }
}, },
removeImage(image) {
let newImages = [];
for (let i in this.item.images) {
if (this.item.images[i] != image) {
newImages.push(this.item.images[i]);
}
}
this.item.images = newImages;
},
}, },
beforeMount() { beforeMount() {
this.getGroups(); this.getGroups();
......
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
> >
<li> <li>
<button <button
to="/user/userItems" @click="this.$router.push('/item/' + item.listingID + '/edit')"
class="block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white" class="block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
> >
Rediger gjenstand Rediger gjenstand
...@@ -117,7 +117,7 @@ ...@@ -117,7 +117,7 @@
> >
<li> <li>
<button <button
to="/user/userItems" @click="this.$router.push('/item/' + item.listingID + '/edit')"
class="block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white" class="block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
> >
Rediger gjenstand Rediger gjenstand
......
...@@ -109,9 +109,9 @@ const routes = [ ...@@ -109,9 +109,9 @@ const routes = [
path: "/community/:communityID/private/join", path: "/community/:communityID/private/join",
name: "communityRequest", name: "communityRequest",
component: () => import("../views/CommunityViews/CommunityRequestView.vue"), component: () => import("../views/CommunityViews/CommunityRequestView.vue"),
beforeEnter: guardRoute,
}, },
{ {
beforeEnter: guardRoute,
path: "/test", path: "/test",
name: "test", name: "test",
component: () => import("../views/TestView.vue"), component: () => import("../views/TestView.vue"),
...@@ -128,10 +128,17 @@ const routes = [ ...@@ -128,10 +128,17 @@ const routes = [
component: () => import("../views/RentingViews/ItemInfoPageView.vue"), component: () => import("../views/RentingViews/ItemInfoPageView.vue"),
beforeEnter: guardRoute, beforeEnter: guardRoute,
}, },
{
path: "/item/:id/edit",
name: "editItem",
component: () => import("../views/ItemViews/EditItemView.vue"),
beforeEnter: guardRoute,
},
{ {
path: "/user/userItems", path: "/user/userItems",
name: "UserItems", name: "UserItems",
component: () => import("../views/UserProfileViews/UserItemsView.vue"), component: () => import("../views/UserProfileViews/UserItemsView.vue"),
beforeEnter: guardRoute,
}, },
// Make sure it's your last route definition // Make sure it's your last route definition
{ path: "/:pathMatch(.*)*", name: "not-found", component: NotFound }, { path: "/:pathMatch(.*)*", name: "not-found", component: NotFound },
......
import { tokenHeader } from "@/utils/token-utils";
import axios from "axios";
const API_URL = process.env.VUE_APP_BASEURL;
class ListingService {
async putItem(itemInfo) {
return await axios
.put(API_URL + "listing/change", itemInfo, {
headers: tokenHeader(),
})
.then((res) => {
return res.data;
})
.catch((err) => console.error(err));
}
async getItem(itemid) {
return await axios
.get(API_URL + "listing/" + itemid, {
headers: tokenHeader(),
})
.then((response) => {
return response.data;
})
.catch((error) => {
console.error(error);
});
}
async getItemPictures(itemid) {
return await axios
.get(API_URL + "listing/" + itemid + "/pictures", {
headers: tokenHeader(),
})
.then((response) => {
return response.data;
})
.catch((error) => {
console.error(error);
});
}
}
export default new ListingService();
<template>
<div class="h-screen grid md:mt-8">
<edit-item-form :initialItem="initialItem" :communities="communities" />
</div>
</template>
<script>
import EditItemForm from "@/components/ItemComponents/EditItemForm.vue";
export default {
name: "EditItemView",
components: {
EditItemForm,
},
};
</script>
...@@ -55,22 +55,57 @@ exports[`NewItemForm component renders correctly 1`] = ` ...@@ -55,22 +55,57 @@ exports[`NewItemForm component renders correctly 1`] = `
<option <option
class="text-gray-900 text-sm" class="text-gray-900 text-sm"
> >
Hage Antikviteter og kunst
</option> </option>
<option <option
class="text-gray-900 text-sm" class="text-gray-900 text-sm"
> >
Kjøkken Dyr og utstyr
</option> </option>
<option <option
class="text-gray-900 text-sm" class="text-gray-900 text-sm"
> >
Musikk Elektronikk og hvitevarer
</option> </option>
<option <option
class="text-gray-900 text-sm" class="text-gray-900 text-sm"
> >
Annet Foreldre og barn
</option>
<option
class="text-gray-900 text-sm"
>
Fritid, hobby og underholdning
</option>
<option
class="text-gray-900 text-sm"
>
Hage, oppussing og hus
</option>
<option
class="text-gray-900 text-sm"
>
Klær, kosmetikk og tilbehør
</option>
<option
class="text-gray-900 text-sm"
>
Møbler og interiør
</option>
<option
class="text-gray-900 text-sm"
>
Næringsvirksomhet
</option>
<option
class="text-gray-900 text-sm"
>
Sport og friluftsliv
</option>
<option
class="text-gray-900 text-sm"
>
Utstyr til bil, båt og MC
</option> </option>
</select> </select>
...@@ -88,7 +123,7 @@ exports[`NewItemForm component renders correctly 1`] = ` ...@@ -88,7 +123,7 @@ exports[`NewItemForm component renders correctly 1`] = `
Grupper Grupper
</label> </label>
<div <div
class="overflow-auto w-full h-32 mt-2 text-base list-none bg-white rounded divide-y divide-gray-100 dark:bg-gray-700" class="overflow-auto w-full max-h-32 mt-2 text-base list-none bg-white rounded divide-y divide-gray-100 dark:bg-gray-700"
> >
<ul <ul
aria-labelledby="dropdownDefault" aria-labelledby="dropdownDefault"
......
...@@ -6,7 +6,7 @@ describe("MonthSelector tests", () => { ...@@ -6,7 +6,7 @@ describe("MonthSelector tests", () => {
beforeEach(() => { beforeEach(() => {
wrapper = shallowMount(monthSelector, { wrapper = shallowMount(monthSelector, {
propsData: { propsData: {
month: new Date(1651739228545), // 05 May 2022 UTC month: new Date(0), // 01 JAN 1970 UTC
type: "type", type: "type",
}, },
}); });
...@@ -25,8 +25,8 @@ describe("MonthSelector tests", () => { ...@@ -25,8 +25,8 @@ describe("MonthSelector tests", () => {
expect(children.length).toBe(2); expect(children.length).toBe(2);
// Check children content // Check children content
expect(children[0].text()).toBe("MAY"); expect(children[0].text()).toBe("JAN");
expect(children[1].text()).toBe("2022"); expect(children[1].text()).toBe("1970");
}); });
it("Check that changing are emitted", async () => { it("Check that changing are emitted", async () => {
...@@ -34,7 +34,6 @@ describe("MonthSelector tests", () => { ...@@ -34,7 +34,6 @@ describe("MonthSelector tests", () => {
expect(wrapper.findAll(".button").length).toBe(2); expect(wrapper.findAll(".button").length).toBe(2);
const buttons = wrapper.findAll(".button"); const buttons = wrapper.findAll(".button");
console.log(buttons[0].html());
// Check that the first button goes a month back // Check that the first button goes a month back
await buttons[0].trigger("click"); await buttons[0].trigger("click");
expect(wrapper.emitted()).toHaveProperty("back"); expect(wrapper.emitted()).toHaveProperty("back");
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment