Adiciona rota para novo video

This commit is contained in:
LeoMortari
2025-09-27 18:38:56 -03:00
parent 541137bcdc
commit a3425562df
7 changed files with 394 additions and 14 deletions

View File

@@ -5,7 +5,8 @@ import { createWebHistory, createRouter } from "vue-router";
import roles from "@/auth/roles";
import Videos from "@/routes/videos";
import Login from "@/routes/auth/Login.vue";
import NewVideo from "@/routes/videos/new";
import Login from "@/routes/auth/Login";
const getUserRoles = () => {
const rolesFromCookie = Cookies.get("user_roles"); // TODO: Tirar as permissões do usuário
@@ -34,6 +35,17 @@ export const routes = [
showinModal: true,
},
},
{
path: "/videos/new",
name: "newVideo",
component: NewVideo,
meta: {
requiresAuth: true,
title: "Novo Vídeo",
permissions: [roles.VIDEOS_LIST],
showinModal: false,
},
},
{
path: "/:pathMatch(.*)*",
meta: {

View File

@@ -0,0 +1,34 @@
<template>
<div>
<span class="display-label">{{ label }}</span>
</div>
<div>
<span>{{ value }}</span>
</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
name: "DisplayValue",
props: {
label: {
type: String,
default: "",
},
value: {
type: [String, Number],
default: "",
},
},
});
</script>
<style scoped>
.display-label {
font-weight: bold;
color: gray;
}
</style>

View File

@@ -0,0 +1,67 @@
<template>
<div>
<div v-if="label">
<span>{{ label }} {{ required ? "*" : "" }}</span>
</div>
<slot
name="select"
:model-value="modelValue"
:update:model-value="updateModelValue"
>
<q-select
outlined
:model-value="modelValue"
@update:model-value="updateModelValue"
:multiple="multiple"
:options="options"
:loading="loading"
:clearable="clearable"
:disable="loading || disable"
/>
</slot>
</div>
</template>
<script setup>
const props = defineProps({
modelValue: {
type: [String, Number, Array, Object],
default: null,
},
label: {
type: String,
default: "",
},
required: {
type: Boolean,
default: false,
},
multiple: {
type: Boolean,
default: false,
},
options: {
type: Array,
required: true,
},
loading: {
type: Boolean,
default: false,
},
clearable: {
type: Boolean,
default: false,
},
disable: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:modelValue"]);
const updateModelValue = (value) => {
emit("update:modelValue", value);
};
</script>

View File

@@ -4,28 +4,56 @@
<span>{{ label }} {{ required ? "*" : "" }}</span>
</div>
<q-input outlined v-model="text" />
<q-input
outlined
:model-value="modelValue"
@update:model-value="updateValue"
:disabled="disabled"
:required="required"
>
<template v-slot:append>
<slot name="append"></slot>
</template>
<template v-slot:prepend>
<slot name="prepend"></slot>
</template>
</q-input>
</div>
</template>
<script>
import { ref } from "vue";
import { defineComponent } from "vue";
export default {
export default defineComponent({
name: "TextField",
props: {
modelValue: {
type: [String, Number],
default: "",
},
label: {
type: String,
required: false,
default: "",
},
required: {
type: Boolean,
required: false,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
},
setup() {
emits: ["update:modelValue"],
setup(props, { emit }) {
const updateValue = (value) => {
emit("update:modelValue", value);
};
return {
text: ref(""),
updateValue,
};
},
};
});
</script>

View File

@@ -2,10 +2,10 @@ import axios from "axios";
import Cookies from "js-cookie";
export const API = axios.create({
baseURL:
process.env.NODE_ENV === "development"
? "https://api.clipperia.com.br"
: "http://nestjs:3000",
baseURL: "http://localhost:3000",
// process.env.NODE_ENV === "development"
// ? "https://api.clipperia.com.br"
// : "http://nestjs:3000",
});
API.interceptors.request.use((config) => {

View File

@@ -16,7 +16,13 @@
<TextField label="Video ID" />
</div>
<div class="col-3">
<q-select outlined :options="['Ativo', 'Inativo']" label="Situação" />
<Dropdown
v-model="select"
label="Situação"
clearable
:options="situations"
:loading="situationsLoading"
/>
</div>
</div>
@@ -29,6 +35,9 @@
fullWidth
/>
</div>
<div class="col-6">
<Button label="Teste" @click="handleTeste()" fullWidth />
</div>
</div>
</q-card>
@@ -50,12 +59,17 @@
</template>
</Table>
</div>
<q-page-sticky position="bottom-right" :offset="[20, 70]">
<q-btn fab icon="add" color="accent" @click="handleAddVideo" />
</q-page-sticky>
</template>
<script>
import Button from "@components/Button";
import Table from "@components/Table";
import TextField from "@components/TextField";
import Dropdown from "@components/Dropdown";
import { API } from "@config/axios";
import { getErrorMessage } from "@utils/axios";
@@ -97,6 +111,7 @@ export default {
Button,
Table,
TextField,
Dropdown,
},
data() {
return {
@@ -112,17 +127,28 @@ export default {
hasNext: false,
hasPrev: false,
},
situations: [],
situationsLoading: false,
select: [],
};
},
mounted() {
this.getSituation();
},
methods: {
async handleSearch(pagination) {
try {
this.loading = true;
const baseParams = {
situation: this.select,
};
const { data } = await API.get("/videos", {
params: {
perPage: pagination.perPage,
page: pagination.page,
...baseParams,
},
});
@@ -140,6 +166,41 @@ export default {
updatePagination(pagination) {
this.handleSearch(pagination);
},
async getSituation() {
try {
this.situationsLoading = true;
const { data } = await API.get("/videos/situacoes");
this.situations = data;
} catch (error) {
this.$q.notify({
type: "negative",
message: getErrorMessage(error, "Erro ao buscar situações"),
});
} finally {
this.situationsLoading = false;
}
},
async handleTeste() {
try {
const { data } = await API.get("/videos/search", {
params: {
url: "https://www.youtube.com/watch?v=x9-YRAYhesI",
},
});
console.log(data);
} catch (error) {
this.$q.notify({
type: "negative",
message: getErrorMessage(error, "Erro ao buscar situações"),
});
}
},
handleAddVideo() {
this.$router.push("/videos/new");
},
},
};
</script>

178
src/routes/videos/new.vue Normal file
View File

@@ -0,0 +1,178 @@
<template>
<div class="user-list q-pa-md">
<q-card
flat
bordered
class="q-pa-sm q-mb-lg"
:class="{
'bg-grey-2': !$q.dark.isActive,
}"
>
<div class="row q-pa-sm">
<div class="col-12">
<TextField label="URL do Vídeo" v-model="url" :disabled="loading" />
</div>
</div>
<div class="row q-pa-sm">
<div class="col-6">
<Button
label="Buscar Informações"
:disabled="!url || loading"
@click="handleSearch"
fullWidth
/>
</div>
</div>
</q-card>
<q-card
flat
bordered
class="q-pa-sm q-mb-lg"
:class="{
'bg-grey-2': !$q.dark.isActive,
}"
>
<div v-if="loading" class="user-list q-pa-md flex justify-center">
<q-spinner color="primary" size="3em" :thickness="10" />
</div>
<div v-else-if="Object.keys(video).length" class="user-list q-pa-md">
<div class="row q-pa-sm">
<span class="text-center text-h6">Vídeo</span>
</div>
<div class="row q-pa-sm">
<div class="col-12">
<DisplayValue label="Título" :value="video.title" />
</div>
</div>
<div class="row q-pa-sm">
<div class="col-3">
<DisplayValue label="Video ID" :value="video.id" />
</div>
<div class="col-3">
<DisplayValue label="Duração" :value="getDuration()" />
</div>
<div class="col-3">
<DisplayValue label="Data de Postagem" :value="getDateVideo()" />
</div>
</div>
</div>
<div v-else class="user-list q-pa-md">
<p class="text-center">
Cole uma URL e faça a busca dos dados do vídeo
</p>
</div>
</q-card>
</div>
</template>
<script>
import dayjs from "dayjs";
import duration from "dayjs/plugin/duration";
import Button from "@components/Button";
import TextField from "@components/TextField";
import DisplayValue from "@components/DisplayValue";
import { API } from "@config/axios";
import { getErrorMessage } from "@utils/axios";
dayjs.extend(duration);
const mock = {
id: "x9-YRAYhesI",
title:
"PROVEI A COMIDA DO EXÉRCITO CHINÊS - TROPA DE ELITE DA CHINA, PACOTE RARO",
thumbnail: "https://i.ytimg.com/vi_webp/x9-YRAYhesI/maxresdefault.webp",
description:
"Hoje iremos experimentar o mais novo pacote de ração militar das forças de elite da China, um dos pacotes de MRE mais difíceis e raros! Vamos comparar para ver se esse pacote é superior ao do exército do Brasil!?",
channel_id: "UCcFgREmujdPHvA7I_VOmgIA",
channel_url: "https://www.youtube.com/channel/UCcFgREmujdPHvA7I_VOmgIA",
duration: 1534,
view_count: 352973,
webpage_url: "https://www.youtube.com/watch?v=x9-YRAYhesI",
categories: ["People & Blogs"],
tags: [
"area secreta",
"mre",
"camping",
"balian",
"acampamento",
"comida do exército",
"ração militar",
"sobrevivi",
"24 horas",
"acampando",
"comida chinesa",
],
comment_count: 785,
like_count: 26175,
channel: "Área Secreta",
channel_follower_count: 10700000,
uploader: "Área Secreta",
timestamp: 1740956718,
};
export default {
name: "NewVideo",
components: {
Button,
TextField,
DisplayValue,
},
data() {
return {
url: "",
loading: false,
video: mock,
};
},
methods: {
async handleSearch() {
if (!this.url) return;
try {
this.loading = true;
const { data } = await API.get("/videos/search", {
params: {
url: this.url,
},
});
this.video = data;
} catch (error) {
this.$q.notify({
type: "negative",
message: getErrorMessage(error, "Erro ao buscar vídeos"),
});
} finally {
this.loading = false;
}
},
getDateVideo() {
const duration = dayjs(this.video.timestamp * 1000);
return duration.format("DD/MM/YYYY HH:mm:ss");
},
getDuration() {
const duration = dayjs.duration(this.video.duration, "seconds");
return duration.format("HH:mm:ss");
},
},
};
</script>
<style scoped>
.user-list {
margin: 0 auto;
}
</style>