OGE Extension
ВСЕ ЧТО НУЖНО ЗНАТЬ ОБ OGE
📜 src/main/index.js
import { app, shell, BrowserWindow, ipcMain, dialog } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
import connectDB from './db';
async function getPartners() {
try {
const response = await global.dbclient.query(`SELECT T1.*,
CASE WHEN sum(T2.production_quantity) > 300000 THEN 15
WHEN sum(T2.production_quantity) > 50000 THEN 10
WHEN sum(T2.production_quantity) > 10000 THEN 5
ELSE 0
END as discount
from partners as T1
LEFT JOIN sales as T2 on T1.id = T2.partner_id
GROUP BY T1.id`)
return response.rows
} catch (e) {
console.log(e)
}
}
async function createPartner(event, partner) {
const { type, name, ceo, email, phone, address, rating } = partner;
try {
await global.dbclient.query(`INSERT into partners (organization_type, name, ceo, email, phone, address, rating) values('${type}', '${name}', '${ceo}', '${email}', '${phone}', '${address}', ${rating})`)
dialog.showMessageBox({ message: 'Успех! Партнер создан' })
} catch (e) {
console.log(e)
dialog.showErrorBox('Ошибка', "Партнер с таким именем уже есть")
}
}
async function updatePartner(event, partner) {
const { id, type, name, ceo, email, phone, address, rating } = partner;
try {
await global.dbclient.query(`UPDATE partners
SET name = '${name}', organization_type = '${type}', ceo='${ceo}', email='${email}', phone='${phone}', address='${address}', rating='${rating}'
WHERE partners.id = ${id};`)
dialog.showMessageBox({ message: 'Успех! Данные обновлены' })
return;
} catch (e) {
dialog.showErrorBox('Невозможно создать пользователя', 'Такой пользователь уже есть')
return ('error')
}
}
function createWindow() {
const mainWindow = new BrowserWindow({
width: 900,
height: 670,
show: false,
icon: join(__dirname, '../../resources/icon.ico'),
autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}
app.whenReady().then(async () => {
electronApp.setAppUserModelId('com.electron')
global.dbclient = await connectDB();
ipcMain.handle('getPartners', getPartners)
ipcMain.handle('createPartner', createPartner)
ipcMain.handle('updatePartner', updatePartner)
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
📜 src/preload/index.js
import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
const api = {
getPartners: () => ipcRenderer.invoke('getPartners'),
createPartner: (partner) => ipcRenderer.invoke('createPartner', partner),
updatePartner: (partner) => ipcRenderer.invoke('updatePartner', partner)
}
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
} catch (error) {
console.error(error)
}
} else {
window.electron = electronAPI
window.api = api
}
📜 src/renderer/index.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>DEMO ELECTRON APP</title>
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
src/renderer/src/App.jsx
import { useEffect, useState } from "react"
import { Link, useNavigate } from "react-router";
import logo from './assets/logo.png'
function App() {
const navigate = useNavigate();
const [partners, setPartners] = useState([]);
useEffect(() => {
(async () => {
const res = await window.api.getPartners()
setPartners(res)
})()
}, [])
return (
<>
<div className="page-heading">
<img className="page-logo" src={logo} alt="" />
<h1>Партнеры</h1>
</div>
<ul className="partners-list">
{partners.map((partner) => {
return <li className="partner-card" key={partner.id} onClick={() => { navigate('/update', { state: { partner } }) }}>
<div className="partner-data">
<p className="card_heading">{partner.organization_type} | {partner.name}</p>
<div className="partner-data-info">
<p>{partner.ceo}</p>
<p>{partner.phone}</p>
<p>Рейтинг: {partner.rating}</p>
</div>
</div>
<div className="partner-sale partner-data card_heading">
{partner.discount}%
</div>
</li>
})}
</ul>
<Link to={'/create'}>
<button>
Создать партнера
</button>
</Link>
</>
)
}
export default App
src/renderer/src/CreatePartner.jsx
import { useEffect } from "react"
import { Link } from "react-router";
export default function CreatePartner() {
useEffect(() => { document.title = 'Создать партнера' }, [])
async function submitHandler(e) {
e.preventDefault()
const partner = {
type: e.target.type.value,
name: e.target.name.value,
ceo: e.target.ceo.value,
email: e.target.email.value,
phone: e.target.phone.value,
address: e.target.address.value,
rating: e.target.rating.value
}
await window.api.createPartner(partner);
document.querySelector('form').reset()
}
return <div className="form">
<Link to={'/'}><button>{"<-- Назад"}</button></Link>
<h1>Создать партнера</h1>
<form onSubmit={(e) => submitHandler(e)}>
<label htmlFor="name">Наименование:</label>
<input id="name" type="text" required />
<label htmlFor="type">Тип партнера:</label>
<select id="type" required>
<option value="ЗАО">ЗАО</option>
<option value="ООО">ООО</option>
<option value="ОАО">ОАО</option>
<option value="ПАО">ПАО</option>
</select>
<label htmlFor="rating">Рейтинг:</label>
<input id="rating" type="number" step="1" min='0' max='100' required />
<label htmlFor="address">Адрес:</label>
<input id="address" type="text" required />
<label htmlFor="ceo">ФИО директора:</label>
<input id="ceo" type="text" required />
<label htmlFor="phone">Телефон:</label>
<input id="phone" type="tel" required />
<label htmlFor="email">Email компании:</label>
<input id="email" type="email" required />
<button type="submit">Создать партнера</button>
</form>
</div>
}
src/renderer/src/UpdatePartner.jsx
import { useEffect, useState } from "react"
import { Link, useLocation } from "react-router";
export default function UpdatePartner() {
useEffect(() => { document.title = 'Обновить партнера' }, [])
const location = useLocation();
const [partner, setPartner] = useState(location.state.partner);
async function submitHandler(e) {
e.preventDefault()
const updPartner = {
id: partner.id,
type: e.target.type.value,
name: e.target.name.value,
ceo: e.target.ceo.value,
email: e.target.email.value,
phone: e.target.phone.value,
address: e.target.address.value,
rating: e.target.rating.value
}
await window.api.updatePartner(updPartner);
setPartner(updPartner)
document.querySelector('form').reset()
}
return <div className="form">
<Link to={'/'}><button>{"<-- Назад"}</button></Link>
<h1>Обновить партнера</h1>
<form onSubmit={(e) => submitHandler(e)}>
<label htmlFor="name">Наименование:</label>
<input id="name" type="text" required defaultValue={partner.name} />
<label htmlFor="type">Тип партнера:</label>
<select id="type" required defaultValue={partner.type} >
<option value="ЗАО">ЗАО</option>
<option value="ООО">ООО</option>
<option value="ОАО">ОАО</option>
<option value="ПАО">ПАО</option>
</select>
<label htmlFor="rating">Рейтинг:</label>
<input id="rating" type="number" step="1" min='0' max='100' required defaultValue={partner.rating}/>
<label htmlFor="address">Адрес:</label>
<input id="address" type="text" required defaultValue={partner.address} />
<label htmlFor="ceo">ФИО директора:</label>
<input id="ceo" type="text" required defaultValue={partner.ceo} />
<label htmlFor="phone">Телефон:</label>
<input id="phone" type="tel" required defaultValue={partner.phone} />
<label htmlFor="email">Email компании:</label>
<input id="email" type="email" required defaultValue={partner.email}/>
<button type="submit">Обновить партнера</button>
</form>
</div>
}
📜 src/renderer/src/main.jsx
import './styles.css'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { Routes, Route, HashRouter } from 'react-router'
import App from './App.jsx'
import UpdatePartner from './UpdatePartner.jsx'
import CreatePartner from './CreatePartner.jsx'
createRoot(document.getElementById('root')).render(
<HashRouter>
<StrictMode>
<Routes>
<Route path='/' element={<App/>}/>
<Route path='/update' element={<UpdatePartner/>}/>
<Route path='/create' element={<CreatePartner/>}/>
</Routes>
</StrictMode>
</HashRouter>
)
src/renderer/src/styles.css
#root {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: 'Segoe UI';
font-size: 18px;
}
p {
margin: 0;
}
.partners-list {
display: flex;
flex-direction: column;
align-items: center;
list-style: none;
border: 1px solid black;
width: 700px;
padding: 20px;
gap: 20px;
}
.partner-card {
display: flex;
justify-content: center;
align-items: center;
min-width: 700px;
justify-content: space-around;
border: 1px solid black;
justify-content: space-between;
}
.partner-card:hover {
background-color: #67BA80;
cursor: pointer;
}
.card_heading {
font-size: 22px;
align-self: start;
}
.page-heading {
display: flex;
flex-direction: row;
width: 700px;
gap: 50px;
}
.page-logo {
width: 120px;
}
.partner-data {
display: flex;
flex-direction: column;
margin: 14px 40px;
}
.form {
display: flex;
flex-direction: column;
}
form {
display: flex;
flex-direction: column;
}
1 этап
1 этап выполняется только средствами СУБД. Пример решения первого этапа представлен в файлах sql.txt и erd.pdf в корне данного каталога. Рекомендуется сразу создать txt файл и записывать туда скрипты для создания таблиц в БД, чтобы впоследствии скрипты можно было легко модифицировать или дропнуть БД и создать с их помощью новую.
Для удобства отладки рекомендуется выполнять скрипты SQL прямо в СУБД (в терминале, либо средствами PGAdmin). Не имеет большого смысла ставить ограничения NOT NULL для большинства полей в таблицах, однако возможно на первом этапе можно обозначить некоторые поля как UNIQUE, чтобы предохраниться в будущем от создания дубликатов.
Для решения задачи рассчета скидки партнера удобнее всего использовать sql, но если не позволяет знания, то всегда можно рассчитать все на сервере и отдать на фронт уже готовые данные.
Для получения ERD можно зайти в PGAdmin, кликнуть на нужную нам БД и нажать ERD for Database, затем в открывшемся меню нажать на download image. Получившееся изображение открыть средствами libre office и сохранить как PDF.