vueでTODO帳の実装

vueでTODO帳の実装

CREATE TABLE WA001_TODO_DETAIL( 
  DB_INS_TMSP TEXT
  , DB_UPD_TMSP TEXT
  , DB_UPD_CNT INTEGER
  , DB_DEL_TMSP TEXT
  , NODE INTEGER
  , WID INTEGER PRIMARY KEY AUTOINCREMENT
  , TITLE TEXT
  , STATUS TEXT
  , MEMO TEXT
)
<style>
li.aitem span{
    display: inline;
    margin:20px 5px;
    text-align: left;
}
li.aitem:hover{
    background-color:#CEE;
}
li.citem span{
    display: inline;
    margin:20px 5px;
}

div.flexBox{
    display:flex;
}
div.flexBox ul{
    text-align: left;
    font-size: 1.5em;
}

li.todoli-0{
    list-style-type:"🔜";
}
li.todoli-1{
    list-style-type:"💯";
}
li.todoli-2{
    list-style-type:"🚧";
}
li.todoli-3{
    list-style-type:"⛓️‍💥";
}
.tooltip {
    font-size: 1.5em;
    position: absolute;
    background-color: #ADD;
    color: #333;
    text-align: center;
    border-radius: 5px;
    padding: 5px;
    z-index: 1;
    visibility: hidden;
    opacity: 0;
    transition: opacity 0.3s;
}

.tooltip.visible {
    visibility: visible;
    opacity: 1;
}

.modal {
    --display: none; /* Hidden by default */
    position: fixed; /* Stay in place */
    z-index: 1; /* Sit on top */
    left: 0;
    top: 0;
    width: 100%; /* Full width */
    height: 100%; /* Full height */
    overflow: auto; /* Enable scroll if needed */
    background-color: rgb(0,0,0); /* Fallback color */
    background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}

.modal-content {
    background-color: #fefefe;
    margin: 15% auto; /* 15% from the top and centered */
    padding: 20px;
    border: 1px solid #888;
    width: 40%; /* Could be more or less, depending on screen size */
}

.close,.save {
    color: #aaa;
    float: right;
    font-size: 28px;
    font-weight: bold;
}

.close:hover,
.close:focus,.save:hover,.save:focus {
    color: black;
    text-decoration: none;
    cursor: pointer;
}

input.mdtitle{
    display: inline;
    width: 80%;
}

input.mdstatus{
    width: 5%;
    display: none;
}

textarea.mdmemo{
    width: 85%;
    min-height: 8em;
}
li span.tmsp{
    color:#aaa;
    font-size:medium;
}
</style>
<template> 

<h2><a href="https://to-do.office.com/tasks/inbox" target="_blank">📐Todo</a></h2>

<div class="flexBox">
<ul>
    <li class="aitem" @click="openEmptyModal">new task</li>
    <li class="aitem" :class="statusMapping(it.STATUS)" v-for="it in items" @contextmenu.prevent="showTooltip($event,it.NODE)">
        <span class="title" > {{it.TITLE}} </span> <span class="tmsp" >({{it.DB_INS_TMSP}})</span>
        <ul class="children" v-show="it.children"> 
            <li class="citem" v-for="it in it.children">
                <span class="status">c-{{it.STATUS}}</span>
                <span class="title" >c-{{it.TITLE}}</span>
            </li>
        </ul>
    </li>
</ul>
</div>
<div class="flexBox">
<ul style="height: 150px;overflow-y: scroll;">
    <li class="aitem" :class="statusMapping(it.STATUS)" v-for="it in items_arch" @contextmenu.prevent="showTooltip($event,it.NODE)">
        <span class="title" > {{it.TITLE}} </span> <span class="tmsp" >({{it.DB_INS_TMSP}})</span>
        <ul class="children" v-show="it.children"> 
            <li class="citem" v-for="it in it.children">
                <span class="status">c-{{it.STATUS}}</span>
                <span class="title" >c-{{it.TITLE}}</span>
            </li>
        </ul>
    </li>
</ul>
</div>
<div class="tooltip" :class="{ visible: isTooltipVisible }" :style="{ top: tooltipY + 'px', left: tooltipX + 'px' }">
    <span class="doing"><li @click="setTodoStatus(0)" class="todoli-0">Start</li></span>
    <span class="finish"><li @click="setTodoStatus(1)" class="todoli-1">Finish</li></span>
    <span class="postpone"><li @click="setTodoStatus(2)" class="todoli-2">Pause</li></span>
    <span class="cancel"><li @click="setTodoStatus(3)" class="todoli-3">Cancel</li></span>
    <span class="doing"><li @click="openModal">Detail</li></span>
</div>

<div class="modal" v-show="isModalVisible">
    <div class="modal-content">
        <span class="close" @click="closeModal">&times;</span>
        <span class="save" @click="saveModal">&copy;</span>
            <div> <input class="mdtitle" v-model="modelCtx.TITLE" />   <input class="mdstatus" v-model="modelCtx.STATUS"/></div>
            <br/>
            <div>  <textarea class="mdmemo" v-model="modelCtx.MEMO"></textarea></div>
    </div>
</div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
const items = ref([])
const items_arch = ref([])

onMounted(async () => {
    await fetchHeadData()
})

const fetchHeadData = async() => {
    const res = await fetch('http://localhost:3000/todo/heads');

    let retJson = await res.json()
    items.value = retJson.filter(it=>{ return ['0','2'].indexOf(it.STATUS)>=0})
    items_arch.value = retJson.filter(it=>{ return ['1','3'].indexOf(it.STATUS)>=0})
}

const statusMapping = (status) => { return "todoli-"+status; }

const isTooltipVisible= ref(false)
const tooltipX = ref(0)
const tooltipY = ref(0)
var currentNode = ref(null)

const modelCtx = ref({
    TITLE:"",
    STATUS:"",
    MEMO:""}
)

const showTooltip = (event,node) => {
    tooltipX.value = event.clientX
    tooltipY.value = event.clientY
    isTooltipVisible.value = true
    currentNode.value = node

    document.addEventListener('click', hideTooltip);
}
const hideTooltip = () => {
    isTooltipVisible.value = false
    document.removeEventListener('click', hideTooltip);
}

const isModalVisible =ref(false)
const openModal = async () => {
    const loading= ref(true);
    const error = ref(null);
    const data = ref()
    try {
        const response = await fetch('http://localhost:3000/todo/getDetail', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ node: currentNode.value })
        });
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        const result = await response.json();
        modelCtx.value = result[0];
        modelCtx.value.TITLE = items.value.filter( o=> o.NODE == currentNode.value).at(0).TITLE

    } catch (err) {
        error.value = 'Failed to post data';
    } finally {
        loading.value = false;
    }
    isModalVisible.value = true
}

const openEmptyModal = () => {
    modelCtx.value = {
    TITLE:"<===>",
    STATUS:"0",
    MEMO:""}
    isModalVisible.value = true
    currentNode.value = "C"
}

const setTodoStatus = async (i) => {
    updateTodoStatus(i)
    window.location.reload()
}

const updateTodoStatus = async (i) => {
    const loading= ref(true);
    const error = ref(null);
    const data = ref({
        TITLE:"",
        STATUS:"",
        MEMO:""}
    )
    data.value.STATUS = i
    try {
        const response = await fetch('http://localhost:3000/todo/updateStatus', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ node: currentNode.value,
                status : data.value.STATUS
             })
        });
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        const result = await response.json();
    } catch (err) {
        error.value = 'Failed to post data';
    } finally {
        loading.value = false;

    }

}

const saveModal = async () => {    
    const loading= ref(true);
    const error = ref(null);
    const data = ref({
        TITLE:"",
        STATUS:"",
        MEMO:""})
    data.value.TITLE = modelCtx.value.TITLE
    data.value.STATUS = modelCtx.value.STATUS
    data.value.MEMO = modelCtx.value.MEMO
    console.log(currentNode.value)
    console.log(data);
    try {
        const response = await fetch('http://localhost:3000/todo/updateDetail', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ node: currentNode.value,
                title : data.value.TITLE,
                status : data.value.STATUS,
                memo: data.value.MEMO
             })
        });
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        const result = await response.json();
        console.log(result);
    } catch (err) {
        error.value = 'Failed to post data';
    } finally {
        loading.value = false;
        isModalVisible.value = false
        window.location.reload()
    }
}

const closeModal = () => {
    isModalVisible.value = false
    window.location.reload()
}

</script>
const express = require('express');
const sqlite3 = require('sqlite3').verbose();
const cors = require('cors');
const bodyParser = require('body-parser');

const app = express();
const port = 3000;

app.use(cors());
app.use(express.json());

const db = new sqlite3.Database('./Main.db');

// データ取得API
app.get('/api/items', (req, res) => {
  db.all('SELECT * FROM PT001_P_LIST_HDR', [], (err, rows) => {
    if (err) {
      res.status(500).json({ error: err.message });
      return;
    }
    res.json(rows);
  });
});

app.post('/getAll', (req, res) => {
  db.all(`SELECT * FROM ${req.body.table} WHERE ${req.body.cond}`, [], (err, rows) => {
    if (err) {
      res.status(500).json({ error: err.message });
      return;
    }
    res.json(rows);
  });
});

app.get('/todo/heads', (req, res) => {
  db.all(`SELECT * FROM (
SELECT NODE, TITLE, STATUS,DB_INS_TMSP,MAX(WID)
  FROM WA001_TODO_DETAIL
  GROUP BY NODE ORDER BY NODE DESC
) WHERE STATUS IN (0,2)  
UNION ALL
SELECT * FROM (
SELECT NODE, TITLE, STATUS,DB_INS_TMSP,MAX(WID)
  FROM WA001_TODO_DETAIL
  GROUP BY NODE ORDER BY DB_INS_TMSP DESC
) WHERE STATUS IN (1,3)  `, [], (err, rows) => {
    if (err) {
      res.status(500).json({ error: err.message });
      return;
    }
    res.json(rows);
  });
});

app.post('/todo/getDetail', (req, res) => {
  db.all(`SELECT NODE, TITLE, STATUS, MEMO, DB_INS_TMSP, MAX(WID)
  FROM WA001_TODO_DETAIL 
  WHERE NODE = ${req.body.node}
  GROUP BY NODE `, [], (err, rows) => {
    if (err) {
      res.status(500).json({ error: err.message });
      return;
    }
    res.json(rows);
  });
});

app.post('/todo/updateDetail', (req, res) => {
  var sql = "";
  if(req.body.node == "C"){
    sql = `INSERT 
    INTO WA001_TODO_DETAIL( DB_INS_TMSP, DB_UPD_TMSP, DB_UPD_CNT, DB_DEL_TMSP, NODE, TITLE, STATUS, MEMO)
  SELECT
    DATETIME('now', 'localtime') as DB_INS_TMSP
    , '0001-1-1 0:00:00' as DB_UPD_TMSP
    , 0 as DB_UPD_CNT
    , '9999-12-31 23:59:59' as DB_DEL_TMSP
    , 1 + MAX(NODE) as NODE
    , '${req.body.title}'  as TITLE
    , '${req.body.status}' as STATUS
    , '${req.body.memo}' as memo 
    FROM
    WA001_TODO_DETAIL`;
  } else {
    sql = `INSERT 
  INTO WA001_TODO_DETAIL( DB_INS_TMSP, DB_UPD_TMSP, DB_UPD_CNT, DB_DEL_TMSP, NODE, TITLE, STATUS, MEMO)
  VALUES ( DATETIME('now', 'localtime'), '0001-1-1 0:00:00', 0, '9999-12-31 23:59:59'
    , '${req.body.node}'
    , '${req.body.title}'
    , '${req.body.status}'
    , '${req.body.memo}')`;
  }

  db.run(sql, [],
    function (err) {
      if (err) {
        return console.error('Insert error:', err.message);
      }
      console.log(`A row has been inserted with rowid ${this.lastID}`);
    }
  );
});


app.post('/todo/updateStatus', (req, res) => {
  db.run(
    `INSERT INTO WA001_TODO_DETAIL( DB_INS_TMSP, DB_UPD_TMSP, DB_UPD_CNT, DB_DEL_TMSP, NODE,TITLE, STATUS, MEMO)
  SELECT DATETIME('now', 'localtime'), '0001-1-1 0:00:00', 0, '9999-12-31 23:59:59', 
  NODE, 
  TITLE,
  '${req.body.status}',
  MEMO
  FROM 
  (SELECT MAX(WID),* FROM WA001_TODO_DETAIL
  WHERE NODE = ${req.body.node}
  GROUP BY NODE)`, [], function (err) {
      if (err) {
        return console.error('Insert error:', err.message);
      }
      console.log(`A row has been inserted with rowid ${this.lastID}`);
    }
  );
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

# Navigate to first project and run Node.js server
Start-Process powershell -ArgumentList "cd 'playground\pj1\sqlite-db1'; node server.js" -NoNewWindow

# Navigate to second project and run Vue dev server
Start-Process powershell -ArgumentList "cd 'playground\pj1'; npm run dev" -NoNewWindow

Start-Process explorer "http://localhost:5099"

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注