News.vue 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. <template>
  2. <div>
  3. <div class="container">
  4. <table class="table is-striped">
  5. <thead>
  6. <tr>
  7. <td>Title</td>
  8. <td>Description</td>
  9. <td>Bugs</td>
  10. <td>Features</td>
  11. <td>Improvements</td>
  12. <td>Upcoming</td>
  13. <td>Options</td>
  14. </tr>
  15. </thead>
  16. <tbody>
  17. <tr v-for="(news, index) in news" :key="index">
  18. <td>
  19. <strong>{{ news.title }}</strong>
  20. </td>
  21. <td>{{ news.description }}</td>
  22. <td>{{ news.bugs.join(", ") }}</td>
  23. <td>{{ news.features.join(", ") }}</td>
  24. <td>{{ news.improvements.join(", ") }}</td>
  25. <td>{{ news.upcoming.join(", ") }}</td>
  26. <td>
  27. <button
  28. class="button is-primary"
  29. @click="editNews(news)"
  30. >
  31. Edit
  32. </button>
  33. <button
  34. class="button is-danger"
  35. @click="removeNews(news)"
  36. >
  37. Remove
  38. </button>
  39. </td>
  40. </tr>
  41. </tbody>
  42. </table>
  43. <div class="card is-fullwidth">
  44. <header class="card-header">
  45. <p class="card-header-title">
  46. Create News
  47. </p>
  48. </header>
  49. <div class="card-content">
  50. <div class="content">
  51. <label class="label">Title & Description</label>
  52. <div class="control is-horizontal">
  53. <div class="control is-grouped">
  54. <p class="control is-expanded">
  55. <input
  56. v-model="creating.title"
  57. class="input"
  58. type="text"
  59. placeholder="Title"
  60. />
  61. </p>
  62. <p class="control is-expanded">
  63. <input
  64. v-model="creating.description"
  65. class="input"
  66. type="text"
  67. placeholder="Short description"
  68. />
  69. </p>
  70. </div>
  71. </div>
  72. <div class="columns">
  73. <div class="column">
  74. <label class="label">Bugs</label>
  75. <p class="control has-addons">
  76. <input
  77. id="new-bugs"
  78. class="input"
  79. type="text"
  80. placeholder="Bug"
  81. @keyup.enter="addChange('bugs')"
  82. />
  83. <a
  84. class="button is-info"
  85. href="#"
  86. @click="addChange('bugs')"
  87. >Add</a
  88. >
  89. </p>
  90. <span
  91. v-for="(bug, index) in creating.bugs"
  92. :key="index"
  93. class="tag is-info"
  94. >
  95. {{ bug }}
  96. <button
  97. class="delete is-info"
  98. @click="removeChange('bugs', index)"
  99. />
  100. </span>
  101. </div>
  102. <div class="column">
  103. <label class="label">Features</label>
  104. <p class="control has-addons">
  105. <input
  106. id="new-features"
  107. class="input"
  108. type="text"
  109. placeholder="Feature"
  110. @keyup.enter="addChange('features')"
  111. />
  112. <a
  113. class="button is-info"
  114. href="#"
  115. @click="addChange('features')"
  116. >Add</a
  117. >
  118. </p>
  119. <span
  120. v-for="(feature,
  121. index) in creating.features"
  122. :key="index"
  123. class="tag is-info"
  124. >
  125. {{ feature }}
  126. <button
  127. class="delete is-info"
  128. @click="removeChange('features', index)"
  129. />
  130. </span>
  131. </div>
  132. </div>
  133. <div class="columns">
  134. <div class="column">
  135. <label class="label">Improvements</label>
  136. <p class="control has-addons">
  137. <input
  138. id="new-improvements"
  139. class="input"
  140. type="text"
  141. placeholder="Improvement"
  142. @keyup.enter="addChange('improvements')"
  143. />
  144. <a
  145. class="button is-info"
  146. href="#"
  147. @click="addChange('improvements')"
  148. >Add</a
  149. >
  150. </p>
  151. <span
  152. v-for="(improvement,
  153. index) in creating.improvements"
  154. :key="index"
  155. class="tag is-info"
  156. >
  157. {{ improvement }}
  158. <button
  159. class="delete is-info"
  160. @click="
  161. removeChange('improvements', index)
  162. "
  163. />
  164. </span>
  165. </div>
  166. <div class="column">
  167. <label class="label">Upcoming</label>
  168. <p class="control has-addons">
  169. <input
  170. id="new-upcoming"
  171. class="input"
  172. type="text"
  173. placeholder="Upcoming"
  174. @keyup.enter="addChange('upcoming')"
  175. />
  176. <a
  177. class="button is-info"
  178. href="#"
  179. @click="addChange('upcoming')"
  180. >Add</a
  181. >
  182. </p>
  183. <span
  184. v-for="(upcoming,
  185. index) in creating.upcoming"
  186. :key="index"
  187. class="tag is-info"
  188. >
  189. {{ upcoming }}
  190. <button
  191. class="delete is-info"
  192. @click="removeChange('upcoming', index)"
  193. />
  194. </span>
  195. </div>
  196. </div>
  197. </div>
  198. </div>
  199. <footer class="card-footer">
  200. <a class="card-footer-item" @click="createNews()" href="#"
  201. >Create</a
  202. >
  203. </footer>
  204. </div>
  205. </div>
  206. <edit-news v-if="modals.editNews" />
  207. </div>
  208. </template>
  209. <script>
  210. import { mapActions, mapState } from "vuex";
  211. import { Toast } from "vue-roaster";
  212. import io from "../../io";
  213. import EditNews from "../Modals/EditNews.vue";
  214. export default {
  215. components: { EditNews },
  216. data() {
  217. return {
  218. news: [],
  219. creating: {
  220. title: "",
  221. description: "",
  222. bugs: [],
  223. features: [],
  224. improvements: [],
  225. upcoming: []
  226. },
  227. editing: {}
  228. };
  229. },
  230. mounted() {
  231. const _this = this;
  232. io.getSocket(socket => {
  233. _this.socket = socket;
  234. _this.socket.emit("news.index", res => {
  235. _this.news = res.data;
  236. return res.data;
  237. });
  238. _this.socket.on("event:admin.news.created", news => {
  239. _this.news.unshift(news);
  240. });
  241. _this.socket.on("event:admin.news.removed", news => {
  242. _this.news = _this.news.filter(item => item._id !== news._id);
  243. });
  244. if (_this.socket.connected) _this.init();
  245. io.onConnect(() => _this.init());
  246. });
  247. },
  248. computed: {
  249. ...mapState("modals", {
  250. modals: state => state.modals.admin
  251. })
  252. },
  253. methods: {
  254. createNews() {
  255. const _this = this;
  256. const {
  257. creating: { bugs, features, improvements, upcoming }
  258. } = this;
  259. if (this.creating.title === "")
  260. return Toast.methods.addToast(
  261. "Field (Title) cannot be empty",
  262. 3000
  263. );
  264. if (this.creating.description === "")
  265. return Toast.methods.addToast(
  266. "Field (Description) cannot be empty",
  267. 3000
  268. );
  269. if (
  270. bugs.length <= 0 &&
  271. features.length <= 0 &&
  272. improvements.length <= 0 &&
  273. upcoming.length <= 0
  274. )
  275. return Toast.methods.addToast(
  276. "You must have at least one News Item",
  277. 3000
  278. );
  279. return _this.socket.emit("news.create", _this.creating, result => {
  280. Toast.methods.addToast(result.message, 4000);
  281. if (result.status === "success")
  282. _this.creating = {
  283. title: "",
  284. description: "",
  285. bugs: [],
  286. features: [],
  287. improvements: [],
  288. upcoming: []
  289. };
  290. });
  291. },
  292. removeNews(news) {
  293. this.socket.emit("news.remove", news, res =>
  294. Toast.methods.addToast(res.message, 8000)
  295. );
  296. },
  297. editNews(news) {
  298. this.editing = news;
  299. this.openModal({ sector: "admin", modal: "editNews" });
  300. },
  301. updateNews(close) {
  302. const _this = this;
  303. this.socket.emit(
  304. "news.update",
  305. _this.editing._id,
  306. _this.editing,
  307. res => {
  308. Toast.methods.addToast(res.message, 4000);
  309. if (res.status === "success") {
  310. if (close)
  311. _this.closeModal({
  312. sector: "admin",
  313. modal: "editNews"
  314. });
  315. }
  316. }
  317. );
  318. },
  319. addChange(type) {
  320. const change = document.getElementById(`new-${type}`).value.trim();
  321. if (this.creating[type].indexOf(change) !== -1)
  322. return Toast.methods.addToast(`Tag already exists`, 3000);
  323. if (change) {
  324. document.getElementById(`new-${type}`).value = "";
  325. this.creating[type].push(change);
  326. return true;
  327. }
  328. return Toast.methods.addToast(`${type} cannot be empty`, 3000);
  329. },
  330. removeChange(type, index) {
  331. this.creating[type].splice(index, 1);
  332. },
  333. init() {
  334. this.socket.emit("apis.joinAdminRoom", "news", () => {});
  335. },
  336. ...mapActions("modals", ["openModal", "closeModal"])
  337. }
  338. };
  339. </script>
  340. <style lang="scss" scoped>
  341. .tag:not(:last-child) {
  342. margin-right: 5px;
  343. }
  344. td {
  345. vertical-align: middle;
  346. }
  347. .is-info:focus {
  348. background-color: #0398db;
  349. }
  350. .card-footer-item {
  351. color: #03a9f4;
  352. }
  353. </style>