Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
195e1589a3 | ||
|
|
0704729e11 | ||
|
|
6958be49cc | ||
|
|
64d433d6e7 |
@ -39,7 +39,7 @@ const config = {
|
|||||||
},
|
},
|
||||||
uploadDir: os.tmpdir(),
|
uploadDir: os.tmpdir(),
|
||||||
email: {
|
email: {
|
||||||
from: 'Multi-Client Detergents POS <app@flatlogic.app>',
|
from: 'نظام إدارة المحل ونقطة البيع <app@flatlogic.app>',
|
||||||
host: 'email-smtp.us-east-1.amazonaws.com',
|
host: 'email-smtp.us-east-1.amazonaws.com',
|
||||||
port: 587,
|
port: 587,
|
||||||
auth: {
|
auth: {
|
||||||
|
|||||||
@ -1,9 +1,3 @@
|
|||||||
const config = require('../../config');
|
|
||||||
const providers = config.providers;
|
|
||||||
const crypto = require('crypto');
|
|
||||||
const bcrypt = require('bcrypt');
|
|
||||||
const moment = require('moment');
|
|
||||||
|
|
||||||
module.exports = function(sequelize, DataTypes) {
|
module.exports = function(sequelize, DataTypes) {
|
||||||
const sales_invoices = sequelize.define(
|
const sales_invoices = sequelize.define(
|
||||||
'sales_invoices',
|
'sales_invoices',
|
||||||
@ -129,22 +123,20 @@ notes: {
|
|||||||
|
|
||||||
sales_invoices.associate = (db) => {
|
sales_invoices.associate = (db) => {
|
||||||
|
|
||||||
db.sales_invoices.belongsToMany(db.sales_invoice_items, {
|
db.sales_invoices.hasMany(db.sales_invoice_items, {
|
||||||
as: 'items',
|
as: 'items',
|
||||||
foreignKey: {
|
foreignKey: {
|
||||||
name: 'sales_invoices_itemsId',
|
name: 'invoiceId',
|
||||||
},
|
},
|
||||||
constraints: false,
|
constraints: false,
|
||||||
through: 'sales_invoicesItemsSales_invoice_items',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
db.sales_invoices.belongsToMany(db.sales_invoice_items, {
|
db.sales_invoices.hasMany(db.sales_invoice_items, {
|
||||||
as: 'items_filter',
|
as: 'items_filter',
|
||||||
foreignKey: {
|
foreignKey: {
|
||||||
name: 'sales_invoices_itemsId',
|
name: 'invoiceId',
|
||||||
},
|
},
|
||||||
constraints: false,
|
constraints: false,
|
||||||
through: 'sales_invoicesItemsSales_invoice_items',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -42,6 +42,7 @@ const sales_invoicesRoutes = require('./routes/sales_invoices');
|
|||||||
const sales_invoice_itemsRoutes = require('./routes/sales_invoice_items');
|
const sales_invoice_itemsRoutes = require('./routes/sales_invoice_items');
|
||||||
|
|
||||||
const price_change_logsRoutes = require('./routes/price_change_logs');
|
const price_change_logsRoutes = require('./routes/price_change_logs');
|
||||||
|
const posRoutes = require('./routes/pos');
|
||||||
|
|
||||||
|
|
||||||
const getBaseUrl = (url) => {
|
const getBaseUrl = (url) => {
|
||||||
@ -54,8 +55,8 @@ const options = {
|
|||||||
openapi: "3.0.0",
|
openapi: "3.0.0",
|
||||||
info: {
|
info: {
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
title: "Multi-Client Detergents POS",
|
title: "نظام إدارة المحل ونقطة البيع",
|
||||||
description: "Multi-Client Detergents POS Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.",
|
description: "واجهة API خاصة بإدارة المحل ونقطة البيع، وتوفّر عمليات الإدارة الأساسية على الكيانات والبيانات.",
|
||||||
},
|
},
|
||||||
servers: [
|
servers: [
|
||||||
{
|
{
|
||||||
@ -120,6 +121,7 @@ app.use('/api/sales_invoices', passport.authenticate('jwt', {session: false}), s
|
|||||||
app.use('/api/sales_invoice_items', passport.authenticate('jwt', {session: false}), sales_invoice_itemsRoutes);
|
app.use('/api/sales_invoice_items', passport.authenticate('jwt', {session: false}), sales_invoice_itemsRoutes);
|
||||||
|
|
||||||
app.use('/api/price_change_logs', passport.authenticate('jwt', {session: false}), price_change_logsRoutes);
|
app.use('/api/price_change_logs', passport.authenticate('jwt', {session: false}), price_change_logsRoutes);
|
||||||
|
app.use('/api/pos', passport.authenticate('jwt', {session: false}), posRoutes);
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
'/api/openai',
|
'/api/openai',
|
||||||
|
|||||||
@ -7,30 +7,23 @@ let publicRoleCache = null;
|
|||||||
|
|
||||||
// Function to asynchronously fetch and cache the 'Public' role
|
// Function to asynchronously fetch and cache the 'Public' role
|
||||||
async function fetchAndCachePublicRole() {
|
async function fetchAndCachePublicRole() {
|
||||||
try {
|
try {
|
||||||
// Use RolesDBApi to find the role by name 'Public'
|
// Use RolesDBApi to find the role by name 'Public'
|
||||||
publicRoleCache = await RolesDBApi.findBy({ name: 'Public' });
|
publicRoleCache = await RolesDBApi.findBy({ name: 'Public' });
|
||||||
|
|
||||||
if (!publicRoleCache) {
|
if (!publicRoleCache) {
|
||||||
console.error("WARNING: Role 'Public' not found in database during middleware startup. Check your migrations.");
|
console.error("WARNING: لم يتم العثور على دور 'Public' في قاعدة البيانات أثناء تهيئة الصلاحيات.");
|
||||||
// The system might not function correctly without this role. May need to throw an error or use a fallback stub.
|
} else {
|
||||||
} else {
|
console.log("تم تحميل دور 'Public' وحفظه في الذاكرة المؤقتة بنجاح.");
|
||||||
console.log("'Public' role successfully loaded and cached.");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching 'Public' role during middleware startup:", error);
|
|
||||||
// Handle the error during startup fetch
|
|
||||||
throw error; // Important to know if the app can proceed without the Public role
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("حدث خطأ أثناء تحميل دور 'Public' عند بدء تشغيل وسيط الصلاحيات:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger the role fetching when the check-permissions.js module is imported/loaded
|
fetchAndCachePublicRole().catch((error) => {
|
||||||
// This should happen during application startup when routes are being configured.
|
console.error('خطأ حرج أثناء تهيئة وسيط الصلاحيات:', error);
|
||||||
fetchAndCachePublicRole().catch(error => {
|
|
||||||
// Handle the case where the fetchAndCachePublicRole promise is rejected
|
|
||||||
console.error("Critical error during permissions middleware initialization:", error);
|
|
||||||
// Decide here if the process should exit if the Public role is essential.
|
|
||||||
// process.exit(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,85 +32,63 @@ fetchAndCachePublicRole().catch(error => {
|
|||||||
* @return {import("express").RequestHandler} Express middleware function.
|
* @return {import("express").RequestHandler} Express middleware function.
|
||||||
*/
|
*/
|
||||||
function checkPermissions(permission) {
|
function checkPermissions(permission) {
|
||||||
return async (req, res, next) => {
|
return async (req, res, next) => {
|
||||||
const { currentUser } = req;
|
const { currentUser } = req;
|
||||||
|
|
||||||
// 1. Check self-access bypass (only if the user is authenticated)
|
if (currentUser && (currentUser.id === req.params.id || currentUser.id === req.body.id)) {
|
||||||
if (currentUser && (currentUser.id === req.params.id || currentUser.id === req.body.id)) {
|
return next();
|
||||||
return next(); // User has access to their own resource
|
}
|
||||||
|
|
||||||
|
if (currentUser) {
|
||||||
|
const customPermissions = Array.isArray(currentUser.custom_permissions)
|
||||||
|
? currentUser.custom_permissions
|
||||||
|
: [];
|
||||||
|
const userPermission = customPermissions.find(
|
||||||
|
(cp) => cp.name === permission,
|
||||||
|
);
|
||||||
|
if (userPermission) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let effectiveRole = null;
|
||||||
|
try {
|
||||||
|
if (currentUser && currentUser.app_role) {
|
||||||
|
effectiveRole = currentUser.app_role;
|
||||||
|
} else if (!publicRoleCache) {
|
||||||
|
console.error("ذاكرة دور 'Public' فارغة. سيتم إعادة تحميله مباشرةً من قاعدة البيانات.");
|
||||||
|
effectiveRole = await RolesDBApi.findBy({ name: 'Public' });
|
||||||
|
if (!effectiveRole) {
|
||||||
|
return next(new Error('خطأ داخلي: تعذر تحميل دور الوصول العام.'));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
effectiveRole = publicRoleCache;
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Check Custom Permissions (only if the user is authenticated)
|
if (!effectiveRole) {
|
||||||
if (currentUser) {
|
return next(new Error('خطأ داخلي: تعذر تحديد الدور الفعّال للتحقق من الصلاحية.'));
|
||||||
// Ensure custom_permissions is an array before using find
|
}
|
||||||
const customPermissions = Array.isArray(currentUser.custom_permissions)
|
|
||||||
? currentUser.custom_permissions
|
|
||||||
: [];
|
|
||||||
const userPermission = customPermissions.find(
|
|
||||||
(cp) => cp.name === permission,
|
|
||||||
);
|
|
||||||
if (userPermission) {
|
|
||||||
return next(); // User has a custom permission
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Determine the "effective" role for permission check
|
let rolePermissions = [];
|
||||||
let effectiveRole = null;
|
if (typeof effectiveRole.getPermissions === 'function') {
|
||||||
try {
|
rolePermissions = await effectiveRole.getPermissions();
|
||||||
if (currentUser && currentUser.app_role) {
|
} else if (Array.isArray(effectiveRole.permissions)) {
|
||||||
// User is authenticated and has an assigned role
|
rolePermissions = effectiveRole.permissions;
|
||||||
effectiveRole = currentUser.app_role;
|
} else {
|
||||||
} else {
|
console.error('تنسيق كائن الدور غير صالح ولا يحتوي على الصلاحيات المطلوبة:', effectiveRole);
|
||||||
// User is NOT authenticated OR is authenticated but has no role
|
return next(new Error('خطأ داخلي: بيانات الدور غير صالحة للتحقق من الصلاحيات.'));
|
||||||
// Use the cached 'Public' role
|
}
|
||||||
if (!publicRoleCache) {
|
|
||||||
// If the cache is unexpectedly empty (e.g., startup error caught),
|
|
||||||
// we can try fetching the role again synchronously (less ideal) or just deny access.
|
|
||||||
console.error("Public role cache is empty. Attempting synchronous fetch...");
|
|
||||||
// Less efficient fallback option:
|
|
||||||
effectiveRole = await RolesDBApi.findBy({ name: 'Public' }); // Could be slow
|
|
||||||
if (!effectiveRole) {
|
|
||||||
// If even the synchronous attempt failed
|
|
||||||
return next(new Error("Internal Server Error: Public role missing and cannot be fetched."));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
effectiveRole = publicRoleCache; // Use the cached object
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we got a valid role object
|
if (rolePermissions.find((p) => p.name === permission)) {
|
||||||
if (!effectiveRole) {
|
next();
|
||||||
return next(new Error("Internal Server Error: Could not determine effective role."));
|
} else {
|
||||||
}
|
next(new ValidationError('auth.forbidden', `Role '${effectiveRole.name || 'unknown'}' denied '${permission}'.`));
|
||||||
|
}
|
||||||
// 4. Check Permissions on the "effective" role
|
} catch (error) {
|
||||||
// Assume the effectiveRole object (from app_role or RolesDBApi) has a getPermissions() method
|
console.error('حدث خطأ أثناء التحقق من الصلاحيات:', error);
|
||||||
// or a 'permissions' property (if permissions are eagerly loaded).
|
next(error);
|
||||||
let rolePermissions = [];
|
}
|
||||||
if (typeof effectiveRole.getPermissions === 'function') {
|
};
|
||||||
rolePermissions = await effectiveRole.getPermissions(); // Get permissions asynchronously if the method exists
|
|
||||||
} else if (Array.isArray(effectiveRole.permissions)) {
|
|
||||||
rolePermissions = effectiveRole.permissions; // Or take from property if permissions are pre-loaded
|
|
||||||
} else {
|
|
||||||
console.error("Role object lacks getPermissions() method or permissions property:", effectiveRole);
|
|
||||||
return next(new Error("Internal Server Error: Invalid role object format."));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (rolePermissions.find((p) => p.name === permission)) {
|
|
||||||
next(); // The "effective" role has the required permission
|
|
||||||
} else {
|
|
||||||
// The "effective" role does not have the required permission
|
|
||||||
const roleName = effectiveRole.name || 'unknown role';
|
|
||||||
next(new ValidationError('auth.forbidden', `Role '${roleName}' denied access to '${permission}'.`));
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
// Handle errors during role or permission fetching
|
|
||||||
console.error("Error during permission check:", e);
|
|
||||||
next(e); // Pass the error to the next middleware
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const METHOD_MAP = {
|
const METHOD_MAP = {
|
||||||
@ -134,16 +105,13 @@ const METHOD_MAP = {
|
|||||||
* @return {import("express").RequestHandler} Express middleware function.
|
* @return {import("express").RequestHandler} Express middleware function.
|
||||||
*/
|
*/
|
||||||
function checkCrudPermissions(name) {
|
function checkCrudPermissions(name) {
|
||||||
return (req, res, next) => {
|
return (req, res, next) => {
|
||||||
// Dynamically determine the permission name (e.g., 'READ_USERS')
|
const permissionName = `${METHOD_MAP[req.method]}_${name.toUpperCase()}`;
|
||||||
const permissionName = `${METHOD_MAP[req.method]}_${name.toUpperCase()}`;
|
checkPermissions(permissionName)(req, res, next);
|
||||||
// Call the checkPermissions middleware with the determined permission
|
};
|
||||||
checkPermissions(permissionName)(req, res, next);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
checkPermissions,
|
checkPermissions,
|
||||||
checkCrudPermissions,
|
checkCrudPermissions,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -13,8 +13,8 @@ const loadRolesModules = () => {
|
|||||||
RolesDBApi: require('../db/api/roles'),
|
RolesDBApi: require('../db/api/roles'),
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Roles modules are missing. Advanced roles are required for this endpoint.', error);
|
console.error('تعذر تحميل وحدات الأدوار المطلوبة لهذا المسار.', error);
|
||||||
const err = new Error('Roles modules are missing. Advanced roles are required for this endpoint.');
|
const err = new Error('تعذر تحميل وحدات الأدوار المطلوبة لهذا المسار.');
|
||||||
err.originalError = error;
|
err.originalError = error;
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@ -310,7 +310,7 @@ router.post(
|
|||||||
if (!prompt) {
|
if (!prompt) {
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Prompt is required',
|
error: 'النص المطلوب للذكاء الاصطناعي فارغ',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ router.get('/image', async (req, res) => {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
res.status(200).json(data.photos[0]);
|
res.status(200).json(data.photos[0]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(200).json({ error: 'Failed to fetch image' });
|
res.status(200).json({ error: 'تعذر جلب الصورة' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ router.get('/video', async (req, res) => {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
res.status(200).json(data.videos[0]);
|
res.status(200).json(data.videos[0]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(200).json({ error: 'Failed to fetch video' });
|
res.status(200).json({ error: 'تعذر جلب الفيديو' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
38
backend/src/routes/pos.js
Normal file
38
backend/src/routes/pos.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
const express = require('express');
|
||||||
|
|
||||||
|
const PosService = require('../services/pos');
|
||||||
|
const wrapAsync = require('../helpers').wrapAsync;
|
||||||
|
const { checkPermissions } = require('../middlewares/check-permissions');
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/workspace',
|
||||||
|
checkPermissions('READ_PRODUCTS'),
|
||||||
|
wrapAsync(async (req, res) => {
|
||||||
|
const payload = await PosService.getWorkspace(req.currentUser, req.query.shopId);
|
||||||
|
res.status(200).send(payload);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/checkout',
|
||||||
|
checkPermissions('CREATE_SALES_INVOICES'),
|
||||||
|
wrapAsync(async (req, res) => {
|
||||||
|
const payload = await PosService.checkout(req.currentUser, req.body);
|
||||||
|
res.status(200).send(payload);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/pricing',
|
||||||
|
checkPermissions('UPDATE_SHOPS'),
|
||||||
|
wrapAsync(async (req, res) => {
|
||||||
|
const payload = await PosService.updatePricing(req.currentUser, req.body);
|
||||||
|
res.status(200).send(payload);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
router.use('/', require('../helpers').commonErrorHandler);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@ -41,15 +41,15 @@ router.post('/', async (req, res) => {
|
|||||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||||
|
|
||||||
if (!searchQuery) {
|
if (!searchQuery) {
|
||||||
return res.status(400).json({ error: 'Please enter a search query' });
|
return res.status(400).json({ error: 'يرجى إدخال عبارة للبحث' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const foundMatches = await SearchService.search(searchQuery, req.currentUser , organizationId, globalAccess,);
|
const foundMatches = await SearchService.search(searchQuery, req.currentUser , organizationId, globalAccess,);
|
||||||
res.json(foundMatches);
|
res.json(foundMatches);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Internal Server Error', error);
|
console.error('حدث خطأ داخلي في البحث', error);
|
||||||
res.status(500).json({ error: 'Internal Server Error' });
|
res.status(500).json({ error: 'حدث خطأ داخلي أثناء تنفيذ البحث' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -38,16 +38,16 @@ router.post(
|
|||||||
wrapAsync(async (req, res) => {
|
wrapAsync(async (req, res) => {
|
||||||
const { sql } = req.body;
|
const { sql } = req.body;
|
||||||
if (typeof sql !== 'string' || !sql.trim()) {
|
if (typeof sql !== 'string' || !sql.trim()) {
|
||||||
return res.status(400).json({ error: 'SQL is required' });
|
return res.status(400).json({ error: 'يرجى إدخال استعلام SQL' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalized = sql.trim().replace(/;+\s*$/, '');
|
const normalized = sql.trim().replace(/;+\s*$/, '');
|
||||||
if (!/^select\b/i.test(normalized)) {
|
if (!/^select\b/i.test(normalized)) {
|
||||||
return res.status(400).json({ error: 'Only SELECT statements are allowed' });
|
return res.status(400).json({ error: 'يسمح فقط باستعلامات SELECT' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalized.includes(';')) {
|
if (normalized.includes(';')) {
|
||||||
return res.status(400).json({ error: 'Only a single SELECT statement is allowed' });
|
return res.status(400).json({ error: 'يسمح باستعلام SELECT واحد فقط في كل طلب' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const rows = await db.sequelize.query(normalized, {
|
const rows = await db.sequelize.query(normalized, {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ const formidable = require('formidable');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { format } = require("util");
|
const { format } = require('util');
|
||||||
|
|
||||||
const ensureDirectoryExistence = (filePath) => {
|
const ensureDirectoryExistence = (filePath) => {
|
||||||
const dirname = path.dirname(filePath);
|
const dirname = path.dirname(filePath);
|
||||||
@ -13,7 +13,7 @@ const ensureDirectoryExistence = (filePath) => {
|
|||||||
|
|
||||||
ensureDirectoryExistence(dirname);
|
ensureDirectoryExistence(dirname);
|
||||||
fs.mkdirSync(dirname);
|
fs.mkdirSync(dirname);
|
||||||
}
|
};
|
||||||
|
|
||||||
const uploadLocal = (
|
const uploadLocal = (
|
||||||
folder,
|
folder,
|
||||||
@ -29,9 +29,7 @@ const uploadLocal = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (validations.entity) {
|
||||||
validations.entity
|
|
||||||
) {
|
|
||||||
res.sendStatus(403);
|
res.sendStatus(403);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -63,7 +61,7 @@ const uploadLocal = (
|
|||||||
|
|
||||||
if (!filename) {
|
if (!filename) {
|
||||||
fs.unlinkSync(fileTempUrl);
|
fs.unlinkSync(fileTempUrl);
|
||||||
res.sendStatus(500);
|
res.status(500).send({ message: 'تعذر تحديد اسم الملف المطلوب رفعه' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,59 +78,57 @@ const uploadLocal = (
|
|||||||
form.on('error', function (err) {
|
form.on('error', function (err) {
|
||||||
res.status(500).send(err);
|
res.status(500).send(err);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const downloadLocal = async (req, res) => {
|
const downloadLocal = async (req, res) => {
|
||||||
const privateUrl = req.query.privateUrl;
|
const privateUrl = req.query.privateUrl;
|
||||||
if (!privateUrl) {
|
if (!privateUrl) {
|
||||||
return res.sendStatus(404);
|
return res.sendStatus(404);
|
||||||
}
|
}
|
||||||
res.download(path.join(config.uploadDir, privateUrl));
|
res.download(path.join(config.uploadDir, privateUrl));
|
||||||
}
|
};
|
||||||
|
|
||||||
const initGCloud = () => {
|
const initGCloud = () => {
|
||||||
const processFile = require("../middlewares/upload");
|
const processFile = require('../middlewares/upload');
|
||||||
const { Storage } = require("@google-cloud/storage");
|
const { Storage } = require('@google-cloud/storage');
|
||||||
|
|
||||||
const crypto = require('crypto')
|
const hash = config.gcloud.hash;
|
||||||
const hash = config.gcloud.hash
|
const privateKey = process.env.GC_PRIVATE_KEY.replace(/\\\n/g, '\n');
|
||||||
|
|
||||||
const privateKey = process.env.GC_PRIVATE_KEY.replace(/\\\n/g, "\n");
|
|
||||||
|
|
||||||
const storage = new Storage({
|
const storage = new Storage({
|
||||||
projectId: process.env.GC_PROJECT_ID,
|
projectId: process.env.GC_PROJECT_ID,
|
||||||
credentials: {
|
credentials: {
|
||||||
client_email: process.env.GC_CLIENT_EMAIL,
|
client_email: process.env.GC_CLIENT_EMAIL,
|
||||||
private_key: privateKey
|
private_key: privateKey,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const bucket = storage.bucket(config.gcloud.bucket);
|
const bucket = storage.bucket(config.gcloud.bucket);
|
||||||
return {hash, bucket, processFile};
|
return { hash, bucket, processFile };
|
||||||
}
|
};
|
||||||
|
|
||||||
const uploadGCloud = async (folder, req, res) => {
|
const uploadGCloud = async (folder, req, res) => {
|
||||||
try {
|
try {
|
||||||
const {hash, bucket, processFile} = initGCloud();
|
const { hash, bucket, processFile } = initGCloud();
|
||||||
await processFile(req, res);
|
await processFile(req, res);
|
||||||
let buffer = await req.file.buffer;
|
|
||||||
let filename = await req.body.filename;
|
|
||||||
|
|
||||||
if (!req.file) {
|
if (!req.file) {
|
||||||
return res.status(400).send({ message: "Please upload a file!" });
|
return res.status(400).send({ message: 'يرجى اختيار ملف للرفع' });
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = `${hash}/${folder}/${filename}`;
|
const buffer = req.file.buffer;
|
||||||
let blob = bucket.file(path);
|
const filename = req.body.filename;
|
||||||
|
const filePath = `${hash}/${folder}/${filename}`;
|
||||||
|
const blob = bucket.file(filePath);
|
||||||
|
|
||||||
console.log(path);
|
console.log(filePath);
|
||||||
|
|
||||||
const blobStream = blob.createWriteStream({
|
const blobStream = blob.createWriteStream({
|
||||||
resumable: false,
|
resumable: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
blobStream.on("error", (err) => {
|
blobStream.on('error', (err) => {
|
||||||
console.log('Upload error');
|
console.log('Upload error');
|
||||||
console.log(err.message);
|
console.log(err.message);
|
||||||
res.status(500).send({ message: err.message });
|
res.status(500).send({ message: err.message });
|
||||||
@ -140,58 +136,58 @@ const uploadGCloud = async (folder, req, res) => {
|
|||||||
|
|
||||||
console.log(`https://storage.googleapis.com/${bucket.name}/${blob.name}`);
|
console.log(`https://storage.googleapis.com/${bucket.name}/${blob.name}`);
|
||||||
|
|
||||||
blobStream.on("finish", async (data) => {
|
blobStream.on('finish', async () => {
|
||||||
const publicUrl = format(
|
const publicUrl = format(
|
||||||
`https://storage.googleapis.com/${bucket.name}/${blob.name}`
|
`https://storage.googleapis.com/${bucket.name}/${blob.name}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
res.status(200).send({
|
res.status(200).send({
|
||||||
message: "Uploaded the file successfully: " + path,
|
message: `تم رفع الملف بنجاح: ${filePath}`,
|
||||||
url: publicUrl,
|
url: publicUrl,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
blobStream.end(buffer)
|
blobStream.end(buffer);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
|
||||||
res.status(500).send({
|
res.status(500).send({
|
||||||
message: `Could not upload the file. ${err}`
|
message: `تعذر رفع الملف. ${err}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const downloadGCloud = async (req, res) => {
|
const downloadGCloud = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const {hash, bucket, processFile} = initGCloud();
|
const { hash, bucket } = initGCloud();
|
||||||
|
|
||||||
const privateUrl = await req.query.privateUrl;
|
const privateUrl = req.query.privateUrl;
|
||||||
const filePath = `${hash}/${privateUrl}`;
|
const filePath = `${hash}/${privateUrl}`;
|
||||||
const file = bucket.file(filePath)
|
const file = bucket.file(filePath);
|
||||||
const fileExists = await file.exists();
|
const fileExists = await file.exists();
|
||||||
|
|
||||||
if (fileExists[0]) {
|
if (fileExists[0]) {
|
||||||
const stream = file.createReadStream();
|
const stream = file.createReadStream();
|
||||||
stream.pipe(res);
|
stream.pipe(res);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
res.status(404).send({
|
res.status(404).send({
|
||||||
message: "Could not download the file. " + err,
|
message: 'تعذر العثور على الملف المطلوب تنزيله',
|
||||||
});
|
});
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(404).send({
|
res.status(404).send({
|
||||||
message: "Could not download the file. " + err,
|
message: 'تعذر تنزيل الملف.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const deleteGCloud = async (privateUrl) => {
|
const deleteGCloud = async (privateUrl) => {
|
||||||
try {
|
try {
|
||||||
const {hash, bucket, processFile} = initGCloud();
|
const { hash, bucket } = initGCloud();
|
||||||
const filePath = `${hash}/${privateUrl}`;
|
const filePath = `${hash}/${privateUrl}`;
|
||||||
|
|
||||||
const file = bucket.file(filePath)
|
const file = bucket.file(filePath);
|
||||||
const fileExists = await file.exists();
|
const fileExists = await file.exists();
|
||||||
|
|
||||||
if (fileExists[0]) {
|
if (fileExists[0]) {
|
||||||
@ -200,7 +196,7 @@ const deleteGCloud = async (privateUrl) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`Cannot find the file ${privateUrl}`);
|
console.log(`Cannot find the file ${privateUrl}`);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
initGCloud,
|
initGCloud,
|
||||||
@ -208,6 +204,5 @@ module.exports = {
|
|||||||
downloadLocal,
|
downloadLocal,
|
||||||
deleteGCloud,
|
deleteGCloud,
|
||||||
uploadGCloud,
|
uploadGCloud,
|
||||||
downloadGCloud
|
downloadGCloud,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,101 +1,96 @@
|
|||||||
const errors = {
|
const errors = {
|
||||||
app: {
|
app: {
|
||||||
title: 'Multi-Client Detergents POS',
|
title: 'نظام إدارة المحل ونقطة البيع',
|
||||||
},
|
},
|
||||||
|
|
||||||
auth: {
|
auth: {
|
||||||
userDisabled: 'Your account is disabled',
|
userDisabled: 'تم تعطيل هذا الحساب',
|
||||||
forbidden: 'Forbidden',
|
forbidden: 'ليس لديك صلاحية لتنفيذ هذا الإجراء',
|
||||||
unauthorized: 'Unauthorized',
|
unauthorized: 'يجب تسجيل الدخول أولاً',
|
||||||
userNotFound: `Sorry, we don't recognize your credentials`,
|
userNotFound: 'البريد الإلكتروني أو كلمة المرور غير صحيحة',
|
||||||
wrongPassword: `Sorry, we don't recognize your credentials`,
|
wrongPassword: 'البريد الإلكتروني أو كلمة المرور غير صحيحة',
|
||||||
weakPassword: 'This password is too weak',
|
weakPassword: 'كلمة المرور ضعيفة جدًا',
|
||||||
emailAlreadyInUse: 'Email is already in use',
|
emailAlreadyInUse: 'هذا البريد الإلكتروني مستخدم بالفعل',
|
||||||
invalidEmail: 'Please provide a valid email',
|
invalidEmail: 'يرجى إدخال بريد إلكتروني صحيح',
|
||||||
passwordReset: {
|
passwordReset: {
|
||||||
invalidToken:
|
invalidToken: 'رابط إعادة تعيين كلمة المرور غير صالح أو منتهي الصلاحية',
|
||||||
'Password reset link is invalid or has expired',
|
error: 'هذا البريد الإلكتروني غير مسجل في النظام',
|
||||||
error: `Email not recognized`,
|
|
||||||
},
|
},
|
||||||
passwordUpdate: {
|
passwordUpdate: {
|
||||||
samePassword: `You can't use the same password. Please create new password`
|
samePassword: 'لا يمكن استخدام كلمة المرور الحالية نفسها. اختر كلمة مرور جديدة',
|
||||||
},
|
},
|
||||||
userNotVerified: `Sorry, your email has not been verified yet`,
|
userNotVerified: 'لم يتم تأكيد البريد الإلكتروني لهذا الحساب بعد',
|
||||||
emailAddressVerificationEmail: {
|
emailAddressVerificationEmail: {
|
||||||
invalidToken:
|
invalidToken: 'رابط تأكيد البريد الإلكتروني غير صالح أو منتهي الصلاحية',
|
||||||
'Email verification link is invalid or has expired',
|
error: 'هذا البريد الإلكتروني غير مسجل في النظام',
|
||||||
error: `Email not recognized`,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
iam: {
|
iam: {
|
||||||
errors: {
|
errors: {
|
||||||
userAlreadyExists:
|
userAlreadyExists: 'يوجد مستخدم بهذا البريد الإلكتروني بالفعل',
|
||||||
'User with this email already exists',
|
userNotFound: 'المستخدم غير موجود',
|
||||||
userNotFound: 'User not found',
|
disablingHimself: 'لا يمكنك تعطيل حسابك الحالي',
|
||||||
disablingHimself: `You can't disable yourself`,
|
revokingOwnPermission: 'لا يمكنك إزالة صلاحية المالك من حسابك الحالي',
|
||||||
revokingOwnPermission: `You can't revoke your own owner permission`,
|
deletingHimself: 'لا يمكنك حذف حسابك الحالي',
|
||||||
deletingHimself: `You can't delete yourself`,
|
emailRequired: 'البريد الإلكتروني مطلوب',
|
||||||
emailRequired: 'Email is required',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
importer: {
|
importer: {
|
||||||
errors: {
|
errors: {
|
||||||
invalidFileEmpty: 'The file is empty',
|
invalidFileEmpty: 'الملف فارغ',
|
||||||
invalidFileExcel:
|
invalidFileExcel: 'يسمح فقط بملفات Excel بصيغة .xlsx',
|
||||||
'Only excel (.xlsx) files are allowed',
|
invalidFileUpload: 'الملف غير صالح. تأكد من استخدام آخر نسخة من القالب المعتمد.',
|
||||||
invalidFileUpload:
|
importHashRequired: 'معرّف الاستيراد مطلوب',
|
||||||
'Invalid file. Make sure you are using the last version of the template.',
|
importHashExistent: 'تم استيراد هذه البيانات مسبقًا',
|
||||||
importHashRequired: 'Import hash is required',
|
userEmailMissing: 'بعض الصفوف في ملف CSV لا تحتوي على بريد إلكتروني',
|
||||||
importHashExistent: 'Data has already been imported',
|
|
||||||
userEmailMissing: 'Some items in the CSV do not have an email',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
errors: {
|
errors: {
|
||||||
forbidden: {
|
forbidden: {
|
||||||
message: 'Forbidden',
|
message: 'ليس لديك صلاحية للوصول',
|
||||||
},
|
},
|
||||||
validation: {
|
validation: {
|
||||||
message: 'An error occurred',
|
message: 'حدث خطأ في التحقق من البيانات',
|
||||||
},
|
},
|
||||||
searchQueryRequired: {
|
searchQueryRequired: {
|
||||||
message: 'Search query is required',
|
message: 'يرجى إدخال عبارة البحث',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
emails: {
|
emails: {
|
||||||
invitation: {
|
invitation: {
|
||||||
subject: `You've been invited to {0}`,
|
subject: 'تمت دعوتك إلى {0}',
|
||||||
body: `
|
body: `
|
||||||
<p>Hello,</p>
|
<p>مرحبًا،</p>
|
||||||
<p>You've been invited to {0} set password for your {1} account.</p>
|
<p>تمت دعوتك إلى {0}. يرجى تعيين كلمة المرور لحسابك في {1}.</p>
|
||||||
<p><a href='{2}'>{2}</a></p>
|
<p><a href='{2}'>{2}</a></p>
|
||||||
<p>Thanks,</p>
|
<p>شكرًا لك،</p>
|
||||||
<p>Your {0} team</p>
|
<p>فريق {0}</p>
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
emailAddressVerification: {
|
emailAddressVerification: {
|
||||||
subject: `Verify your email for {0}`,
|
subject: 'تأكيد البريد الإلكتروني لـ {0}',
|
||||||
body: `
|
body: `
|
||||||
<p>Hello,</p>
|
<p>مرحبًا،</p>
|
||||||
<p>Follow this link to verify your email address.</p>
|
<p>استخدم هذا الرابط لتأكيد عنوان بريدك الإلكتروني.</p>
|
||||||
<p><a href='{0}'>{0}</a></p>
|
<p><a href='{0}'>{0}</a></p>
|
||||||
<p>If you didn't ask to verify this address, you can ignore this email.</p>
|
<p>إذا لم تطلب هذا الإجراء، يمكنك تجاهل هذه الرسالة.</p>
|
||||||
<p>Thanks,</p>
|
<p>شكرًا لك،</p>
|
||||||
<p>Your {1} team</p>
|
<p>فريق {1}</p>
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
passwordReset: {
|
passwordReset: {
|
||||||
subject: `Reset your password for {0}`,
|
subject: 'إعادة تعيين كلمة المرور لـ {0}',
|
||||||
body: `
|
body: `
|
||||||
<p>Hello,</p>
|
<p>مرحبًا،</p>
|
||||||
<p>Follow this link to reset your {0} password for your {1} account.</p>
|
<p>استخدم هذا الرابط لإعادة تعيين كلمة المرور الخاصة بك في {0} لحساب {1}.</p>
|
||||||
<p><a href='{2}'>{2}</a></p>
|
<p><a href='{2}'>{2}</a></p>
|
||||||
<p>If you didn't ask to reset your password, you can ignore this email.</p>
|
<p>إذا لم تطلب إعادة التعيين، يمكنك تجاهل هذه الرسالة.</p>
|
||||||
<p>Thanks,</p>
|
<p>شكرًا لك،</p>
|
||||||
<p>Your {0} team</p>
|
<p>فريق {0}</p>
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,8 +6,8 @@ const loadRoleService = () => {
|
|||||||
try {
|
try {
|
||||||
return require('./roles');
|
return require('./roles');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Role service is missing. Advanced roles are required for this operation.', error);
|
console.error('تعذر تحميل خدمة الأدوار المطلوبة لهذه العملية.', error);
|
||||||
const err = new Error('Role service is missing. Advanced roles are required for this operation.');
|
const err = new Error('تعذر تحميل خدمة الأدوار المطلوبة لهذه العملية.');
|
||||||
err.originalError = error;
|
err.originalError = error;
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@ -25,17 +25,17 @@ module.exports = class OpenAiService {
|
|||||||
const { widget_id } = await response.data;
|
const { widget_id } = await response.data;
|
||||||
await RoleService.addRoleInfo(roleId, userId, 'widgets', widget_id);
|
await RoleService.addRoleInfo(roleId, userId, 'widgets', widget_id);
|
||||||
return widget_id;
|
return widget_id;
|
||||||
} else {
|
|
||||||
console.error('=======error=======', response.data);
|
|
||||||
return { value: null, error: response.data };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.error('Widget generation failed:', response.data);
|
||||||
|
return { value: null, error: response.data };
|
||||||
}
|
}
|
||||||
|
|
||||||
static async askGpt(prompt) {
|
static async askGpt(prompt) {
|
||||||
if (!prompt) {
|
if (!prompt) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Prompt is required'
|
error: 'النص المطلوب للذكاء الاصطناعي فارغ',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ module.exports = class OpenAiService {
|
|||||||
console.error('AI JSON decode failed:', error);
|
console.error('AI JSON decode failed:', error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: 'AI response parsing failed',
|
error: 'تعذر قراءة رد الذكاء الاصطناعي',
|
||||||
details: error.message || String(error),
|
details: error.message || String(error),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -73,7 +73,7 @@ module.exports = class OpenAiService {
|
|||||||
console.error('AI proxy error:', response);
|
console.error('AI proxy error:', response);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: response.error || response.message || 'AI proxy error',
|
error: response.error || response.message || 'حدث خطأ أثناء التواصل مع خدمة الذكاء الاصطناعي',
|
||||||
response,
|
response,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
560
backend/src/services/pos.js
Normal file
560
backend/src/services/pos.js
Normal file
@ -0,0 +1,560 @@
|
|||||||
|
const db = require('../db/models');
|
||||||
|
const ValidationError = require('./notifications/errors/validation');
|
||||||
|
|
||||||
|
const { Op } = db.Sequelize;
|
||||||
|
|
||||||
|
const PAYMENT_METHODS = new Set(['cash', 'card', 'transfer', 'mixed']);
|
||||||
|
const PRICING_ACTIONS = new Set(['set_rate', 'apply_prices', 'restore_prices']);
|
||||||
|
|
||||||
|
const toNumber = (value, fallback = 0) => {
|
||||||
|
const parsed = Number.parseFloat(String(value ?? ''));
|
||||||
|
return Number.isFinite(parsed) ? parsed : fallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
const roundMoney = (value) => Number(toNumber(value).toFixed(2));
|
||||||
|
|
||||||
|
const formatInvoiceNumber = () => {
|
||||||
|
const stamp = new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0, 14);
|
||||||
|
const suffix = Math.floor(Math.random() * 900 + 100);
|
||||||
|
return `INV-${stamp}-${suffix}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildOrgWhere = (currentUser) => {
|
||||||
|
if (currentUser?.app_role?.globalAccess) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentUser?.organizationsId) {
|
||||||
|
return { id: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
organizationsId: currentUser.organizationsId,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildShopWhere = (currentUser, shopId) => {
|
||||||
|
const orgWhere = buildOrgWhere(currentUser);
|
||||||
|
|
||||||
|
if (orgWhere.id === null) {
|
||||||
|
return orgWhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...orgWhere,
|
||||||
|
...(shopId ? { id: shopId } : {}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStartAndEndOfToday = () => {
|
||||||
|
const start = new Date();
|
||||||
|
start.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const end = new Date(start);
|
||||||
|
end.setDate(end.getDate() + 1);
|
||||||
|
|
||||||
|
return { start, end };
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapInvoice = (invoice) => ({
|
||||||
|
id: invoice.id,
|
||||||
|
invoice_number: invoice.invoice_number,
|
||||||
|
sold_at: invoice.sold_at,
|
||||||
|
total_amount: roundMoney(invoice.total_amount),
|
||||||
|
total_profit_amount: roundMoney(invoice.total_profit_amount),
|
||||||
|
payment_method: invoice.payment_method,
|
||||||
|
item_count: (invoice.sales_invoice_items_invoice || []).reduce(
|
||||||
|
(sum, item) => sum + toNumber(item.quantity, 0),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
cashier_name: [invoice.cashier?.firstName, invoice.cashier?.lastName]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')
|
||||||
|
.trim(),
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = class PosService {
|
||||||
|
static async getWorkspace(currentUser, shopId) {
|
||||||
|
const shops = await db.shops.findAll({
|
||||||
|
where: buildShopWhere(currentUser),
|
||||||
|
attributes: [
|
||||||
|
'id',
|
||||||
|
'shop_name',
|
||||||
|
'currency_name',
|
||||||
|
'usd_rate',
|
||||||
|
'allow_negative_stock',
|
||||||
|
'is_active',
|
||||||
|
'organizationsId',
|
||||||
|
],
|
||||||
|
order: [['createdAt', 'ASC']],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!shops.length) {
|
||||||
|
return {
|
||||||
|
shops: [],
|
||||||
|
selectedShop: null,
|
||||||
|
categories: [],
|
||||||
|
products: [],
|
||||||
|
summary: {
|
||||||
|
totalSales: 0,
|
||||||
|
totalProfit: 0,
|
||||||
|
invoiceCount: 0,
|
||||||
|
},
|
||||||
|
recentInvoices: [],
|
||||||
|
latestPriceChange: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedShop =
|
||||||
|
shops.find((shop) => shop.id === shopId) ||
|
||||||
|
shops[0];
|
||||||
|
|
||||||
|
const categories = await db.categories.findAll({
|
||||||
|
where: {
|
||||||
|
shopId: selectedShop.id,
|
||||||
|
...buildOrgWhere(currentUser),
|
||||||
|
},
|
||||||
|
attributes: ['id', 'category_name', 'description'],
|
||||||
|
order: [
|
||||||
|
['sort_order', 'ASC'],
|
||||||
|
['category_name', 'ASC'],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const products = await db.products.findAll({
|
||||||
|
where: {
|
||||||
|
shopId: selectedShop.id,
|
||||||
|
...buildOrgWhere(currentUser),
|
||||||
|
},
|
||||||
|
attributes: [
|
||||||
|
'id',
|
||||||
|
'product_name',
|
||||||
|
'sku',
|
||||||
|
'barcode',
|
||||||
|
'cost_price',
|
||||||
|
'sale_price',
|
||||||
|
'sale_price_backup',
|
||||||
|
'usd_price',
|
||||||
|
'stock_quantity',
|
||||||
|
'is_active',
|
||||||
|
'categoryId',
|
||||||
|
],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: db.categories,
|
||||||
|
as: 'category',
|
||||||
|
attributes: ['id', 'category_name'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
order: [
|
||||||
|
['categoryId', 'ASC'],
|
||||||
|
['product_name', 'ASC'],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { start, end } = getStartAndEndOfToday();
|
||||||
|
|
||||||
|
const invoiceWhere = {
|
||||||
|
shopId: selectedShop.id,
|
||||||
|
...buildOrgWhere(currentUser),
|
||||||
|
sold_at: {
|
||||||
|
[Op.gte]: start,
|
||||||
|
[Op.lt]: end,
|
||||||
|
},
|
||||||
|
status: 'paid',
|
||||||
|
};
|
||||||
|
|
||||||
|
const summaryInvoices = await db.sales_invoices.findAll({
|
||||||
|
where: invoiceWhere,
|
||||||
|
attributes: ['id', 'total_amount', 'total_profit_amount'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const invoices = await db.sales_invoices.findAll({
|
||||||
|
where: invoiceWhere,
|
||||||
|
attributes: [
|
||||||
|
'id',
|
||||||
|
'invoice_number',
|
||||||
|
'sold_at',
|
||||||
|
'total_amount',
|
||||||
|
'total_profit_amount',
|
||||||
|
'payment_method',
|
||||||
|
],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: db.sales_invoice_items,
|
||||||
|
as: 'sales_invoice_items_invoice',
|
||||||
|
attributes: ['id', 'quantity'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: db.users,
|
||||||
|
as: 'cashier',
|
||||||
|
attributes: ['id', 'firstName', 'lastName'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
order: [['sold_at', 'DESC']],
|
||||||
|
limit: 12,
|
||||||
|
});
|
||||||
|
|
||||||
|
const summary = summaryInvoices.reduce(
|
||||||
|
(acc, invoice) => ({
|
||||||
|
totalSales: acc.totalSales + toNumber(invoice.total_amount),
|
||||||
|
totalProfit: acc.totalProfit + toNumber(invoice.total_profit_amount),
|
||||||
|
invoiceCount: acc.invoiceCount + 1,
|
||||||
|
}),
|
||||||
|
{ totalSales: 0, totalProfit: 0, invoiceCount: 0 },
|
||||||
|
);
|
||||||
|
|
||||||
|
const latestPriceChange = await db.price_change_logs.findOne({
|
||||||
|
where: {
|
||||||
|
shopId: selectedShop.id,
|
||||||
|
...buildOrgWhere(currentUser),
|
||||||
|
},
|
||||||
|
order: [
|
||||||
|
['changed_at', 'DESC'],
|
||||||
|
['createdAt', 'DESC'],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
shops: shops.map((shop) => ({
|
||||||
|
id: shop.id,
|
||||||
|
shop_name: shop.shop_name,
|
||||||
|
currency_name: shop.currency_name,
|
||||||
|
usd_rate: roundMoney(shop.usd_rate),
|
||||||
|
allow_negative_stock: Boolean(shop.allow_negative_stock),
|
||||||
|
is_active: Boolean(shop.is_active),
|
||||||
|
})),
|
||||||
|
selectedShop: {
|
||||||
|
id: selectedShop.id,
|
||||||
|
shop_name: selectedShop.shop_name,
|
||||||
|
currency_name: selectedShop.currency_name,
|
||||||
|
usd_rate: roundMoney(selectedShop.usd_rate),
|
||||||
|
allow_negative_stock: Boolean(selectedShop.allow_negative_stock),
|
||||||
|
is_active: Boolean(selectedShop.is_active),
|
||||||
|
},
|
||||||
|
categories: categories.map((category) => ({
|
||||||
|
id: category.id,
|
||||||
|
category_name: category.category_name,
|
||||||
|
description: category.description,
|
||||||
|
})),
|
||||||
|
products: products.map((product) => ({
|
||||||
|
id: product.id,
|
||||||
|
product_name: product.product_name,
|
||||||
|
sku: product.sku,
|
||||||
|
barcode: product.barcode,
|
||||||
|
cost_price: roundMoney(product.cost_price),
|
||||||
|
sale_price: roundMoney(product.sale_price),
|
||||||
|
sale_price_backup: roundMoney(product.sale_price_backup),
|
||||||
|
usd_price: product.usd_price == null ? null : roundMoney(product.usd_price),
|
||||||
|
stock_quantity: product.stock_quantity,
|
||||||
|
is_active: Boolean(product.is_active),
|
||||||
|
categoryId: product.categoryId,
|
||||||
|
category_name: product.category?.category_name || 'بدون قسم',
|
||||||
|
})),
|
||||||
|
summary: {
|
||||||
|
totalSales: roundMoney(summary.totalSales),
|
||||||
|
totalProfit: roundMoney(summary.totalProfit),
|
||||||
|
invoiceCount: summary.invoiceCount,
|
||||||
|
},
|
||||||
|
recentInvoices: invoices.map(mapInvoice),
|
||||||
|
latestPriceChange: latestPriceChange
|
||||||
|
? {
|
||||||
|
id: latestPriceChange.id,
|
||||||
|
changed_at: latestPriceChange.changed_at || latestPriceChange.createdAt,
|
||||||
|
change_type: latestPriceChange.change_type,
|
||||||
|
usd_rate_before: roundMoney(latestPriceChange.usd_rate_before),
|
||||||
|
usd_rate_after: roundMoney(latestPriceChange.usd_rate_after),
|
||||||
|
summary: latestPriceChange.summary,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async checkout(currentUser, payload) {
|
||||||
|
const transaction = await db.sequelize.transaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rawItems = Array.isArray(payload?.items) ? payload.items : [];
|
||||||
|
const normalizedItems = rawItems
|
||||||
|
.map((item) => ({
|
||||||
|
productId: item?.productId,
|
||||||
|
quantity: Number.parseInt(String(item?.quantity ?? ''), 10),
|
||||||
|
}))
|
||||||
|
.filter((item) => item.productId && Number.isInteger(item.quantity) && item.quantity > 0);
|
||||||
|
|
||||||
|
if (!normalizedItems.length) {
|
||||||
|
throw new ValidationError('errors.validation.message');
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentMethod = String(payload?.paymentMethod || 'cash');
|
||||||
|
if (!PAYMENT_METHODS.has(paymentMethod)) {
|
||||||
|
throw new ValidationError('errors.validation.message');
|
||||||
|
}
|
||||||
|
|
||||||
|
const shop = await db.shops.findOne({
|
||||||
|
where: buildShopWhere(currentUser, payload?.shopId),
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!shop) {
|
||||||
|
throw new ValidationError('errors.validation.message');
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueProductIds = [...new Set(normalizedItems.map((item) => item.productId))];
|
||||||
|
const products = await db.products.findAll({
|
||||||
|
where: {
|
||||||
|
id: uniqueProductIds,
|
||||||
|
shopId: shop.id,
|
||||||
|
...buildOrgWhere(currentUser),
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (products.length !== uniqueProductIds.length) {
|
||||||
|
throw new ValidationError('errors.validation.message');
|
||||||
|
}
|
||||||
|
|
||||||
|
const productMap = new Map(products.map((product) => [product.id, product]));
|
||||||
|
let subtotal = 0;
|
||||||
|
let totalCost = 0;
|
||||||
|
const lineItems = [];
|
||||||
|
|
||||||
|
for (const item of normalizedItems) {
|
||||||
|
const product = productMap.get(item.productId);
|
||||||
|
const salePrice = toNumber(product.sale_price);
|
||||||
|
const costPrice = toNumber(product.cost_price);
|
||||||
|
const stockQuantity = product.stock_quantity;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!shop.allow_negative_stock &&
|
||||||
|
stockQuantity != null &&
|
||||||
|
Number.isFinite(stockQuantity) &&
|
||||||
|
stockQuantity < item.quantity
|
||||||
|
) {
|
||||||
|
throw new Error(`الكمية غير كافية للمنتج: ${product.product_name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineSubtotal = roundMoney(salePrice * item.quantity);
|
||||||
|
const lineCost = roundMoney(costPrice * item.quantity);
|
||||||
|
const lineProfit = roundMoney(lineSubtotal - lineCost);
|
||||||
|
|
||||||
|
subtotal += lineSubtotal;
|
||||||
|
totalCost += lineCost;
|
||||||
|
|
||||||
|
lineItems.push({
|
||||||
|
product,
|
||||||
|
quantity: item.quantity,
|
||||||
|
lineSubtotal,
|
||||||
|
lineProfit,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalAmount = roundMoney(subtotal);
|
||||||
|
const totalCostAmount = roundMoney(totalCost);
|
||||||
|
const totalProfitAmount = roundMoney(totalAmount - totalCostAmount);
|
||||||
|
const soldAt = new Date();
|
||||||
|
|
||||||
|
const invoice = await db.sales_invoices.create(
|
||||||
|
{
|
||||||
|
invoice_number: formatInvoiceNumber(),
|
||||||
|
sold_at: soldAt,
|
||||||
|
status: 'paid',
|
||||||
|
subtotal_amount: totalAmount,
|
||||||
|
discount_amount: 0,
|
||||||
|
total_amount: totalAmount,
|
||||||
|
total_cost_amount: totalCostAmount,
|
||||||
|
total_profit_amount: totalProfitAmount,
|
||||||
|
payment_method: paymentMethod,
|
||||||
|
notes: payload?.notes || null,
|
||||||
|
shopId: shop.id,
|
||||||
|
cashierId: currentUser.id,
|
||||||
|
organizationsId: currentUser.organizationsId || shop.organizationsId || null,
|
||||||
|
createdById: currentUser.id,
|
||||||
|
updatedById: currentUser.id,
|
||||||
|
},
|
||||||
|
{ transaction },
|
||||||
|
);
|
||||||
|
|
||||||
|
await db.sales_invoice_items.bulkCreate(
|
||||||
|
lineItems.map((item) => ({
|
||||||
|
product_name_snapshot: item.product.product_name,
|
||||||
|
cost_price_snapshot: roundMoney(item.product.cost_price),
|
||||||
|
sale_price_snapshot: roundMoney(item.product.sale_price),
|
||||||
|
quantity: item.quantity,
|
||||||
|
line_subtotal: item.lineSubtotal,
|
||||||
|
line_profit: item.lineProfit,
|
||||||
|
invoiceId: invoice.id,
|
||||||
|
productId: item.product.id,
|
||||||
|
organizationsId: currentUser.organizationsId || shop.organizationsId || null,
|
||||||
|
createdById: currentUser.id,
|
||||||
|
updatedById: currentUser.id,
|
||||||
|
})),
|
||||||
|
{ transaction },
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const item of lineItems) {
|
||||||
|
if (item.product.stock_quantity != null) {
|
||||||
|
await item.product.update(
|
||||||
|
{
|
||||||
|
stock_quantity: toNumber(item.product.stock_quantity) - item.quantity,
|
||||||
|
updatedById: currentUser.id,
|
||||||
|
},
|
||||||
|
{ transaction },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await transaction.commit();
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: invoice.id,
|
||||||
|
invoice_number: invoice.invoice_number,
|
||||||
|
sold_at: soldAt,
|
||||||
|
total_amount: totalAmount,
|
||||||
|
total_profit_amount: totalProfitAmount,
|
||||||
|
items_count: lineItems.reduce((sum, item) => sum + item.quantity, 0),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await transaction.rollback();
|
||||||
|
console.error('POS checkout failed:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updatePricing(currentUser, payload) {
|
||||||
|
const transaction = await db.sequelize.transaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const action = String(payload?.action || '');
|
||||||
|
if (!PRICING_ACTIONS.has(action)) {
|
||||||
|
throw new ValidationError('errors.validation.message');
|
||||||
|
}
|
||||||
|
|
||||||
|
const shop = await db.shops.findOne({
|
||||||
|
where: buildShopWhere(currentUser, payload?.shopId),
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!shop) {
|
||||||
|
throw new ValidationError('errors.validation.message');
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingRate = toNumber(shop.usd_rate, 0);
|
||||||
|
const incomingRate = toNumber(payload?.usdRate, existingRate);
|
||||||
|
|
||||||
|
if ((action === 'set_rate' || action === 'apply_prices') && incomingRate <= 0) {
|
||||||
|
throw new Error('يرجى إدخال سعر دولار صحيح أكبر من صفر.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const products = await db.products.findAll({
|
||||||
|
where: {
|
||||||
|
shopId: shop.id,
|
||||||
|
...buildOrgWhere(currentUser),
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (action === 'set_rate' || action === 'apply_prices') {
|
||||||
|
await shop.update(
|
||||||
|
{
|
||||||
|
usd_rate: incomingRate,
|
||||||
|
updatedById: currentUser.id,
|
||||||
|
},
|
||||||
|
{ transaction },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let changedProducts = 0;
|
||||||
|
let message = '';
|
||||||
|
let changeType = 'usd_rate_update';
|
||||||
|
|
||||||
|
if (action === 'apply_prices') {
|
||||||
|
changeType = 'bulk_increase_by_usd';
|
||||||
|
|
||||||
|
for (const product of products) {
|
||||||
|
const currentSalePrice = toNumber(product.sale_price, 0);
|
||||||
|
const baseUsdPrice =
|
||||||
|
product.usd_price != null
|
||||||
|
? toNumber(product.usd_price, 0)
|
||||||
|
: existingRate > 0
|
||||||
|
? roundMoney(currentSalePrice / existingRate)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const updatedSalePrice = roundMoney(baseUsdPrice * incomingRate);
|
||||||
|
|
||||||
|
await product.update(
|
||||||
|
{
|
||||||
|
sale_price_backup: currentSalePrice,
|
||||||
|
usd_price: baseUsdPrice,
|
||||||
|
sale_price: updatedSalePrice,
|
||||||
|
updatedById: currentUser.id,
|
||||||
|
},
|
||||||
|
{ transaction },
|
||||||
|
);
|
||||||
|
changedProducts += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = `تم تحديث أسعار البيع لعدد ${changedProducts} منتج حسب سعر الدولار.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'restore_prices') {
|
||||||
|
changeType = 'bulk_restore_previous';
|
||||||
|
|
||||||
|
for (const product of products) {
|
||||||
|
if (product.sale_price_backup == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await product.update(
|
||||||
|
{
|
||||||
|
sale_price: product.sale_price_backup,
|
||||||
|
sale_price_backup: null,
|
||||||
|
updatedById: currentUser.id,
|
||||||
|
},
|
||||||
|
{ transaction },
|
||||||
|
);
|
||||||
|
changedProducts += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = changedProducts
|
||||||
|
? `تمت إعادة ${changedProducts} سعر إلى القيمة السابقة.`
|
||||||
|
: 'لا توجد أسعار محفوظة للاسترجاع حالياً.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'set_rate') {
|
||||||
|
message = `تم حفظ سعر الدولار الجديد للمحل بنجاح.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.price_change_logs.create(
|
||||||
|
{
|
||||||
|
changed_at: new Date(),
|
||||||
|
change_type: changeType,
|
||||||
|
usd_rate_before: existingRate || null,
|
||||||
|
usd_rate_after: action === 'restore_prices' ? existingRate || null : incomingRate,
|
||||||
|
summary: message,
|
||||||
|
shopId: shop.id,
|
||||||
|
changed_byId: currentUser.id,
|
||||||
|
organizationsId: currentUser.organizationsId || shop.organizationsId || null,
|
||||||
|
createdById: currentUser.id,
|
||||||
|
updatedById: currentUser.id,
|
||||||
|
},
|
||||||
|
{ transaction },
|
||||||
|
);
|
||||||
|
|
||||||
|
await transaction.commit();
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
action,
|
||||||
|
shopId: shop.id,
|
||||||
|
usdRate: action === 'restore_prices' ? roundMoney(shop.usd_rate) : roundMoney(incomingRate),
|
||||||
|
changedProducts,
|
||||||
|
message,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await transaction.rollback();
|
||||||
|
console.error('POS pricing update failed:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
10
frontend/lint-final.log
Normal file
10
frontend/lint-final.log
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
> lint
|
||||||
|
> eslint . --ext .ts,.tsx
|
||||||
|
|
||||||
|
|
||||||
|
/home/ubuntu/executor/workspace/frontend/src/components/Logo/index.tsx
|
||||||
|
9:5 warning Do not use `<img>` element. Use `<Image />` from `next/image` instead. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
|
||||||
|
|
||||||
|
✖ 1 problem (0 errors, 1 warning)
|
||||||
|
|
||||||
2
frontend/next-env.d.ts
vendored
2
frontend/next-env.d.ts
vendored
@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
/// <reference path="./build/types/routes.d.ts" />
|
/// <reference path="./.next/types/routes.d.ts" />
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
|
||||||
|
|||||||
@ -63,13 +63,13 @@ const AsideMenuItem = ({ item, isDropdownList = false }: Props) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const componentClass = [
|
const componentClass = [
|
||||||
'flex cursor-pointer py-1.5 ',
|
'flex cursor-pointer py-1.5 font-medium transition-all duration-200 ease-out',
|
||||||
isDropdownList ? 'px-6 text-sm' : '',
|
isDropdownList ? 'px-6 text-sm' : '',
|
||||||
item.color
|
item.color
|
||||||
? getButtonColor(item.color, false, true)
|
? getButtonColor(item.color, false, true)
|
||||||
: `${asideMenuItemStyle}`,
|
: `${asideMenuItemStyle}`,
|
||||||
isLinkActive
|
isLinkActive
|
||||||
? `text-black ${activeLinkColor} dark:text-white dark:bg-dark-800`
|
? `text-black ${activeLinkColor} shadow-sm dark:text-white dark:bg-dark-800`
|
||||||
: '',
|
: '',
|
||||||
].join(' ');
|
].join(' ');
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,10 @@ import { mdiLogout, mdiClose } from '@mdi/js'
|
|||||||
import BaseIcon from './BaseIcon'
|
import BaseIcon from './BaseIcon'
|
||||||
import AsideMenuList from './AsideMenuList'
|
import AsideMenuList from './AsideMenuList'
|
||||||
import { MenuAsideItem } from '../interfaces'
|
import { MenuAsideItem } from '../interfaces'
|
||||||
import { useAppSelector } from '../stores/hooks'
|
import Link from 'next/link'
|
||||||
import Link from 'next/link';
|
import { useAppDispatch, useAppSelector } from '../stores/hooks'
|
||||||
|
import { createAsyncThunk } from '@reduxjs/toolkit'
|
||||||
import { useAppDispatch } from '../stores/hooks';
|
import axios from 'axios'
|
||||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -58,17 +56,17 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props
|
|||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
id='asideMenu'
|
id='asideMenu'
|
||||||
className={`${className} zzz lg:py-2 lg:pl-2 w-60 fixed flex z-40 top-0 h-screen transition-position overflow-hidden`}
|
className={`${className} zzz lg:py-2 lg:pl-2 w-60 fixed top-0 z-40 flex h-screen overflow-hidden transition-all duration-300 ease-out`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex-1 flex flex-col overflow-hidden dark:bg-dark-900 ${asideStyle} ${corners}`}
|
className={`flex flex-1 flex-col overflow-hidden border border-slate-200/70 shadow-sm transition-all duration-300 ease-out dark:bg-dark-900 dark:border-dark-700 ${asideStyle} ${corners}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex flex-row h-14 items-center justify-between ${asideBrandStyle}`}
|
className={`flex flex-row h-14 items-center justify-between ${asideBrandStyle}`}
|
||||||
>
|
>
|
||||||
<div className="text-center flex-1 lg:text-left lg:pl-6 xl:text-center xl:pl-0">
|
<div className="text-center flex-1 lg:text-left lg:pl-6 xl:text-center xl:pl-0">
|
||||||
|
|
||||||
<b className="font-black">Multi-Client Detergents POS</b>
|
<b className="font-black tracking-[0.01em]">Multi-Client Detergents POS</b>
|
||||||
|
|
||||||
|
|
||||||
{organizationName && <p>{organizationName}</p>}
|
{organizationName && <p>{organizationName}</p>}
|
||||||
|
|||||||
@ -48,10 +48,13 @@ export default function BaseButton({
|
|||||||
'justify-center',
|
'justify-center',
|
||||||
'items-center',
|
'items-center',
|
||||||
'whitespace-nowrap',
|
'whitespace-nowrap',
|
||||||
|
'font-semibold',
|
||||||
|
'tracking-[0.01em]',
|
||||||
'focus:outline-none',
|
'focus:outline-none',
|
||||||
'transition-colors',
|
'transition-all',
|
||||||
|
'duration-200',
|
||||||
|
'ease-out',
|
||||||
'focus:ring',
|
'focus:ring',
|
||||||
'duration-150',
|
|
||||||
'border',
|
'border',
|
||||||
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
|
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
|
||||||
roundedFull ? 'rounded-full' : `${corners}`,
|
roundedFull ? 'rounded-full' : `${corners}`,
|
||||||
@ -60,7 +63,7 @@ export default function BaseButton({
|
|||||||
]
|
]
|
||||||
|
|
||||||
if (!label && icon) {
|
if (!label && icon) {
|
||||||
componentClass.push('p-1')
|
componentClass.push('p-1.5')
|
||||||
} else if (small) {
|
} else if (small) {
|
||||||
componentClass.push('text-sm', roundedFull ? 'px-3 py-1' : 'p-1')
|
componentClass.push('text-sm', roundedFull ? 'px-3 py-1' : 'p-1')
|
||||||
} else {
|
} else {
|
||||||
@ -69,6 +72,8 @@ export default function BaseButton({
|
|||||||
|
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
componentClass.push(outline ? 'opacity-50' : 'opacity-70')
|
componentClass.push(outline ? 'opacity-50' : 'opacity-70')
|
||||||
|
} else {
|
||||||
|
componentClass.push('hover:-translate-y-px', 'hover:shadow-sm', 'active:translate-y-0')
|
||||||
}
|
}
|
||||||
|
|
||||||
const componentClassString = componentClass.join(' ')
|
const componentClassString = componentClass.join(' ')
|
||||||
|
|||||||
@ -37,16 +37,16 @@ export default function CardBox({
|
|||||||
const corners = useAppSelector((state) => state.style.corners);
|
const corners = useAppSelector((state) => state.style.corners);
|
||||||
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
|
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
|
||||||
const componentClass = [
|
const componentClass = [
|
||||||
`flex dark:border-dark-700 dark:bg-dark-900`,
|
`flex dark:border-dark-700 dark:bg-dark-900 transition-all duration-300 ease-out motion-reduce:transition-none`,
|
||||||
className,
|
className,
|
||||||
corners !== 'rounded-full'? corners : 'rounded-3xl',
|
corners !== 'rounded-full'? corners : 'rounded-3xl',
|
||||||
flex,
|
flex,
|
||||||
isList ? '' : `${cardsStyle}`,
|
isList ? '' : `${cardsStyle}`,
|
||||||
hasTable ? '' : `border-dark-700 dark:border-dark-700`,
|
hasTable ? '' : `border-dark-700 dark:border-dark-700`,
|
||||||
]
|
]
|
||||||
|
|
||||||
if (isHoverable) {
|
if (isHoverable) {
|
||||||
componentClass.push('hover:shadow-lg transition-shadow duration-500')
|
componentClass.push('hover:-translate-y-0.5 hover:shadow-lg')
|
||||||
}
|
}
|
||||||
|
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
|
|||||||
@ -33,7 +33,7 @@ const CardBoxModal = ({
|
|||||||
const footer = (
|
const footer = (
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton label={buttonLabel} color={buttonColor} onClick={onConfirm} />
|
<BaseButton label={buttonLabel} color={buttonColor} onClick={onConfirm} />
|
||||||
{!!onCancel && <BaseButton label="Cancel" color={buttonColor} outline onClick={onCancel} />}
|
{!!onCancel && <BaseButton label="إلغاء" color={buttonColor} outline onClick={onCancel} />}
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -90,7 +90,7 @@ const CardCategories = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>اسمالقسم</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>اسم القسم</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.category_name }
|
{ item.category_name }
|
||||||
@ -114,7 +114,7 @@ const CardCategories = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>ترتيبالعرض</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>ترتيب العرض</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.sort_order }
|
{ item.sort_order }
|
||||||
@ -141,7 +141,7 @@ const CardCategories = ({
|
|||||||
))}
|
))}
|
||||||
{!loading && categories.length === 0 && (
|
{!loading && categories.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -56,7 +56,7 @@ const ListCategories = ({ categories, loading, onDelete, currentPage, numPages,
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>اسمالقسم</p>
|
<p className={'text-xs text-gray-500 '}>اسم القسم</p>
|
||||||
<p className={'line-clamp-2'}>{ item.category_name }</p>
|
<p className={'line-clamp-2'}>{ item.category_name }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ const ListCategories = ({ categories, loading, onDelete, currentPage, numPages,
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>ترتيبالعرض</p>
|
<p className={'text-xs text-gray-500 '}>ترتيب العرض</p>
|
||||||
<p className={'line-clamp-2'}>{ item.sort_order }</p>
|
<p className={'line-clamp-2'}>{ item.sort_order }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ const ListCategories = ({ categories, loading, onDelete, currentPage, numPages,
|
|||||||
))}
|
))}
|
||||||
{!loading && categories.length === 0 && (
|
{!loading && categories.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -277,7 +277,7 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">التصفية</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -311,7 +311,7 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">اختر قيمة</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +326,22 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">من</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder='من'
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">إلى</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder='إلى'
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -356,12 +356,12 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>
|
||||||
From
|
من
|
||||||
</div>
|
</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder='من'
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +369,11 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>إلى</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder='إلى'
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +383,11 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">يحتوي على</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder='أدخل قيمة'
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +395,12 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">الإجراء</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label='حذف'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +413,13 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label='تطبيق'
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label='إلغاء'
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +429,14 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title="تأكيد العملية"
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? 'جارٍ الحذف...' : 'تأكيد'}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>هل أنت متأكد أنك تريد حذف هذا العنصر؟</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +450,7 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`حذف ${selectedRows.length === 1 ? 'صف واحد' : 'عدة صفوف'}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -65,7 +65,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'category_name',
|
field: 'category_name',
|
||||||
headerName: 'اسمالقسم',
|
headerName: 'اسم القسم',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -95,7 +95,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'sort_order',
|
field: 'sort_order',
|
||||||
headerName: 'ترتيبالعرض',
|
headerName: 'ترتيب العرض',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
|
|||||||
@ -42,7 +42,7 @@ const KanbanCard = ({
|
|||||||
href={`/${entityName}/${entityName}-view/?id=${item.id}`}
|
href={`/${entityName}/${entityName}-view/?id=${item.id}`}
|
||||||
className={'text-base font-semibold'}
|
className={'text-base font-semibold'}
|
||||||
>
|
>
|
||||||
{item[showFieldName] ?? 'No data'}
|
{item[showFieldName] ?? 'لا توجد بيانات'}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex items-center justify-between'}>
|
<div className={'flex items-center justify-between'}>
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'next/link';
|
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import BaseIcon from './BaseIcon';
|
import BaseIcon from './BaseIcon';
|
||||||
import {
|
import {
|
||||||
@ -81,7 +80,7 @@ const ListActionsPopover = ({
|
|||||||
href={linkView}
|
href={linkView}
|
||||||
sx={{ justifyContent: "start" }}
|
sx={{ justifyContent: "start" }}
|
||||||
>
|
>
|
||||||
View
|
عرض
|
||||||
</Button>
|
</Button>
|
||||||
{hasUpdatePermission && (
|
{hasUpdatePermission && (
|
||||||
<Button
|
<Button
|
||||||
@ -90,7 +89,7 @@ const ListActionsPopover = ({
|
|||||||
href={linkEdit}
|
href={linkEdit}
|
||||||
sx={{ justifyContent: "start" }}
|
sx={{ justifyContent: "start" }}
|
||||||
>
|
>
|
||||||
Edit
|
تعديل
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{hasUpdatePermission && (
|
{hasUpdatePermission && (
|
||||||
@ -103,7 +102,7 @@ const ListActionsPopover = ({
|
|||||||
}}
|
}}
|
||||||
sx={{ justifyContent: "start" }}
|
sx={{ justifyContent: "start" }}
|
||||||
>
|
>
|
||||||
Delete
|
حذف
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -35,9 +35,9 @@ export default function NavBar({ menu, className = '', children }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
className={`${className} top-0 inset-x-0 fixed ${bgColor} h-14 z-30 transition-position w-screen lg:w-auto dark:bg-dark-800`}
|
className={`${className} top-0 inset-x-0 fixed ${bgColor} h-14 z-30 w-screen transition-all duration-300 ease-out backdrop-blur-sm lg:w-auto dark:bg-dark-800`}
|
||||||
>
|
>
|
||||||
<div className={`flex lg:items-stretch ${containerMaxW} ${isScrolled && `border-b border-pavitra-400 dark:border-dark-700`}`}>
|
<div className={`flex lg:items-stretch ${containerMaxW} ${isScrolled ? 'border-b border-pavitra-400/70 shadow-sm shadow-slate-200/60 dark:border-dark-700 dark:shadow-none' : ''}`}>
|
||||||
<div className="flex flex-1 items-stretch h-14">{children}</div>
|
<div className="flex flex-1 items-stretch h-14">{children}</div>
|
||||||
<div className="flex-none items-stretch flex h-14 lg:hidden">
|
<div className="flex-none items-stretch flex h-14 lg:hidden">
|
||||||
<NavBarItemPlain onClick={handleMenuNavBarToggleClick}>
|
<NavBarItemPlain onClick={handleMenuNavBarToggleClick}>
|
||||||
@ -47,7 +47,7 @@ export default function NavBar({ menu, className = '', children }: Props) {
|
|||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
isMenuNavBarActive ? 'block' : 'hidden'
|
isMenuNavBarActive ? 'block' : 'hidden'
|
||||||
} flex items-center max-h-screen-menu overflow-y-auto lg:overflow-visible absolute w-screen top-14 left-0 ${bgColor} shadow-lg lg:w-auto lg:flex lg:static lg:shadow-none dark:bg-dark-800`}
|
} absolute left-0 top-14 flex max-h-screen-menu w-screen items-center overflow-y-auto ${bgColor} shadow-lg transition-all duration-300 ease-out lg:static lg:w-auto lg:flex lg:overflow-visible lg:shadow-none dark:bg-dark-800`}
|
||||||
>
|
>
|
||||||
<NavBarMenuList menu={menu} />
|
<NavBarMenuList menu={menu} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import React, {useEffect, useRef} from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useState } from 'react'
|
|
||||||
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
|
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
|
||||||
import BaseDivider from './BaseDivider'
|
import BaseDivider from './BaseDivider'
|
||||||
import BaseIcon from './BaseIcon'
|
import BaseIcon from './BaseIcon'
|
||||||
@ -10,8 +9,8 @@ import { useAppDispatch, useAppSelector } from '../stores/hooks'
|
|||||||
import { MenuNavBarItem } from '../interfaces'
|
import { MenuNavBarItem } from '../interfaces'
|
||||||
import { setDarkMode } from '../stores/styleSlice'
|
import { setDarkMode } from '../stores/styleSlice'
|
||||||
import { logoutUser } from '../stores/authSlice'
|
import { logoutUser } from '../stores/authSlice'
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router'
|
||||||
import ClickOutside from "./ClickOutside";
|
import ClickOutside from './ClickOutside'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
item: MenuNavBarItem
|
item: MenuNavBarItem
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export default function NavBarItemPlain({
|
|||||||
const navBarItemLabelStyle = useAppSelector((state) => state.style.navBarItemLabelStyle)
|
const navBarItemLabelStyle = useAppSelector((state) => state.style.navBarItemLabelStyle)
|
||||||
const navBarItemLabelHoverStyle = useAppSelector((state) => state.style.navBarItemLabelHoverStyle)
|
const navBarItemLabelHoverStyle = useAppSelector((state) => state.style.navBarItemLabelHoverStyle)
|
||||||
|
|
||||||
const classBase = 'items-center cursor-pointer dark:text-white dark:hover:text-slate-400'
|
const classBase = 'items-center cursor-pointer font-medium transition-colors duration-200 ease-out dark:text-white dark:hover:text-slate-400'
|
||||||
const classAddon = `${display} ${navBarItemLabelStyle} ${navBarItemLabelHoverStyle} ${
|
const classAddon = `${display} ${navBarItemLabelStyle} ${navBarItemLabelHoverStyle} ${
|
||||||
useMargin ? 'my-2 mx-3' : 'py-2 px-3'
|
useMargin ? 'my-2 mx-3' : 'py-2 px-3'
|
||||||
}`
|
}`
|
||||||
|
|||||||
@ -93,7 +93,7 @@ const CardOrganizations = ({
|
|||||||
))}
|
))}
|
||||||
{!loading && organizations.length === 0 && (
|
{!loading && organizations.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -70,7 +70,7 @@ const ListOrganizations = ({ organizations, loading, onDelete, currentPage, numP
|
|||||||
))}
|
))}
|
||||||
{!loading && organizations.length === 0 && (
|
{!loading && organizations.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -395,12 +395,12 @@ const TableSampleOrganizations = ({ filterItems, setFilterItems, filters, showGr
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">إجراء</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label='حذف'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +413,13 @@ const TableSampleOrganizations = ({ filterItems, setFilterItems, filters, showGr
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label='تطبيق'
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label='إلغاء'
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +429,14 @@ const TableSampleOrganizations = ({ filterItems, setFilterItems, filters, showGr
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title="تأكيد العملية"
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? 'جارٍ الحذف...' : 'تأكيد'}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>هل أنت متأكد من حذف هذا العنصر؟</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +450,7 @@ const TableSampleOrganizations = ({ filterItems, setFilterItems, filters, showGr
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`حذف ${selectedRows.length === 1 ? 'السجل المحدد' : 'السجلات المحددة'}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -49,16 +49,16 @@ export default function PasswordSetOrReset() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
{isInvitation && <title>{getPageTitle('Set Password')}</title>}
|
{isInvitation && <title>{getPageTitle('تعيين كلمة المرور')}</title>}
|
||||||
{!isInvitation && <title>{getPageTitle('Reset Password')}</title>}
|
{!isInvitation && <title>{getPageTitle('إعادة تعيين كلمة المرور')}</title>}
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<SectionFullScreen bg='violet'>
|
<SectionFullScreen bg='violet'>
|
||||||
<div className='w-full flex flex-col items-center justify-center'>
|
<div className='w-full flex flex-col items-center justify-center'>
|
||||||
<CardBox className='w-11/12 md:w-7/12 lg:w-6/12 xl:w-4/12'>
|
<CardBox className='w-11/12 md:w-7/12 lg:w-6/12 xl:w-4/12'>
|
||||||
{isInvitation && <p className='text-xl mb-2'>Set Password</p>}
|
{isInvitation && <p className='text-xl mb-2'>تعيين كلمة المرور</p>}
|
||||||
{!isInvitation && <p className='text-xl mb-2'>Reset Password</p>}
|
{!isInvitation && <p className='text-xl mb-2'>إعادة تعيين كلمة المرور</p>}
|
||||||
<p className='text-base mb-4'>Enter your new password</p>
|
<p className='text-base mb-4'>أدخل كلمة المرور الجديدة</p>
|
||||||
|
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{
|
initialValues={{
|
||||||
@ -74,7 +74,7 @@ export default function PasswordSetOrReset() {
|
|||||||
<Field
|
<Field
|
||||||
type='password'
|
type='password'
|
||||||
name='password'
|
name='password'
|
||||||
placeholder='Password'
|
placeholder='كلمة المرور الجديدة'
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField
|
<FormField
|
||||||
@ -82,7 +82,7 @@ export default function PasswordSetOrReset() {
|
|||||||
<Field
|
<Field
|
||||||
type='password'
|
type='password'
|
||||||
name='confirm'
|
name='confirm'
|
||||||
placeholder='Confirm Password'
|
placeholder='تأكيد كلمة المرور'
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -93,10 +93,10 @@ export default function PasswordSetOrReset() {
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
label={
|
label={
|
||||||
loading
|
loading
|
||||||
? 'Loading...'
|
? 'جارٍ الحفظ...'
|
||||||
: isInvitation
|
: isInvitation
|
||||||
? 'Set Password'
|
? 'تعيين كلمة المرور'
|
||||||
: 'Reset Password'
|
: 'إعادة تعيين كلمة المرور'
|
||||||
}
|
}
|
||||||
color='info'
|
color='info'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -93,7 +93,7 @@ const CardPermissions = ({
|
|||||||
))}
|
))}
|
||||||
{!loading && permissions.length === 0 && (
|
{!loading && permissions.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -70,7 +70,7 @@ const ListPermissions = ({ permissions, loading, onDelete, currentPage, numPages
|
|||||||
))}
|
))}
|
||||||
{!loading && permissions.length === 0 && (
|
{!loading && permissions.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -395,12 +395,12 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">إجراء</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label='حذف'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +413,13 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label='تطبيق'
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label='إلغاء'
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +429,14 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title="تأكيد العملية"
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? 'جارٍ الحذف...' : 'تأكيد'}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>هل أنت متأكد من حذف هذا العنصر؟</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +450,7 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`حذف ${selectedRows.length === 1 ? 'السجل المحدد' : 'السجلات المحددة'}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -165,7 +165,7 @@ const CardPrice_change_logs = ({
|
|||||||
))}
|
))}
|
||||||
{!loading && price_change_logs.length === 0 && (
|
{!loading && price_change_logs.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -118,7 +118,7 @@ const ListPrice_change_logs = ({ price_change_logs, loading, onDelete, currentPa
|
|||||||
))}
|
))}
|
||||||
{!loading && price_change_logs.length === 0 && (
|
{!loading && price_change_logs.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -395,12 +395,12 @@ const TableSamplePrice_change_logs = ({ filterItems, setFilterItems, filters, sh
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">إجراء</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label='حذف'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +413,13 @@ const TableSamplePrice_change_logs = ({ filterItems, setFilterItems, filters, sh
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label='تطبيق'
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label='إلغاء'
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +429,14 @@ const TableSamplePrice_change_logs = ({ filterItems, setFilterItems, filters, sh
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title="تأكيد العملية"
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? 'جارٍ الحذف...' : 'تأكيد'}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>هل أنت متأكد من حذف هذا العنصر؟</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +450,7 @@ const TableSamplePrice_change_logs = ({ filterItems, setFilterItems, filters, sh
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`حذف ${selectedRows.length === 1 ? 'السجل المحدد' : 'السجلات المحددة'}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -111,7 +111,7 @@ const CardProducts = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>اسمالمنتج</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>اسم المنتج</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.product_name }
|
{ item.product_name }
|
||||||
@ -123,7 +123,7 @@ const CardProducts = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>رمزالمنتج</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>رمز المنتج</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.sku }
|
{ item.sku }
|
||||||
@ -147,7 +147,7 @@ const CardProducts = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>سعرالتكلفةالحقيقي</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>سعر التكلفة</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.cost_price }
|
{ item.cost_price }
|
||||||
@ -159,7 +159,7 @@ const CardProducts = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>سعرالبيع</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>سعر البيع</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.sale_price }
|
{ item.sale_price }
|
||||||
@ -171,7 +171,7 @@ const CardProducts = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>سعرالبيعالسابق</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>سعر البيع السابق</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.sale_price_backup }
|
{ item.sale_price_backup }
|
||||||
@ -183,7 +183,7 @@ const CardProducts = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>سعرالتكلفةالسابق</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>سعر التكلفة السابق</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.cost_price_backup }
|
{ item.cost_price_backup }
|
||||||
@ -195,7 +195,7 @@ const CardProducts = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>السعربالدولار</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>السعر بالدولار</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.usd_price }
|
{ item.usd_price }
|
||||||
@ -207,7 +207,7 @@ const CardProducts = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>الكميةبالمخزون</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>الكمية بالمخزون</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.stock_quantity }
|
{ item.stock_quantity }
|
||||||
@ -219,7 +219,7 @@ const CardProducts = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>حدالتنبيهلنقصالمخزون</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>حد التنبيه لنقص المخزون</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.low_stock_threshold }
|
{ item.low_stock_threshold }
|
||||||
@ -247,7 +247,7 @@ const CardProducts = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>متاحللبيع</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>متاح للبيع</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ dataFormatter.booleanFormatter(item.is_active) }
|
{ dataFormatter.booleanFormatter(item.is_active) }
|
||||||
@ -262,7 +262,7 @@ const CardProducts = ({
|
|||||||
))}
|
))}
|
||||||
{!loading && products.length === 0 && (
|
{!loading && products.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -71,7 +71,7 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>اسمالمنتج</p>
|
<p className={'text-xs text-gray-500 '}>اسم المنتج</p>
|
||||||
<p className={'line-clamp-2'}>{ item.product_name }</p>
|
<p className={'line-clamp-2'}>{ item.product_name }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>رمزالمنتج</p>
|
<p className={'text-xs text-gray-500 '}>رمز المنتج</p>
|
||||||
<p className={'line-clamp-2'}>{ item.sku }</p>
|
<p className={'line-clamp-2'}>{ item.sku }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>سعرالتكلفةالحقيقي</p>
|
<p className={'text-xs text-gray-500 '}>سعر التكلفة</p>
|
||||||
<p className={'line-clamp-2'}>{ item.cost_price }</p>
|
<p className={'line-clamp-2'}>{ item.cost_price }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>سعرالبيع</p>
|
<p className={'text-xs text-gray-500 '}>سعر البيع</p>
|
||||||
<p className={'line-clamp-2'}>{ item.sale_price }</p>
|
<p className={'line-clamp-2'}>{ item.sale_price }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>سعرالبيعالسابق</p>
|
<p className={'text-xs text-gray-500 '}>سعر البيع السابق</p>
|
||||||
<p className={'line-clamp-2'}>{ item.sale_price_backup }</p>
|
<p className={'line-clamp-2'}>{ item.sale_price_backup }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>سعرالتكلفةالسابق</p>
|
<p className={'text-xs text-gray-500 '}>سعر التكلفة السابق</p>
|
||||||
<p className={'line-clamp-2'}>{ item.cost_price_backup }</p>
|
<p className={'line-clamp-2'}>{ item.cost_price_backup }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -127,7 +127,7 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>السعربالدولار</p>
|
<p className={'text-xs text-gray-500 '}>السعر بالدولار</p>
|
||||||
<p className={'line-clamp-2'}>{ item.usd_price }</p>
|
<p className={'line-clamp-2'}>{ item.usd_price }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>الكميةبالمخزون</p>
|
<p className={'text-xs text-gray-500 '}>الكمية بالمخزون</p>
|
||||||
<p className={'line-clamp-2'}>{ item.stock_quantity }</p>
|
<p className={'line-clamp-2'}>{ item.stock_quantity }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>حدالتنبيهلنقصالمخزون</p>
|
<p className={'text-xs text-gray-500 '}>حد التنبيه لنقص المخزون</p>
|
||||||
<p className={'line-clamp-2'}>{ item.low_stock_threshold }</p>
|
<p className={'line-clamp-2'}>{ item.low_stock_threshold }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>متاحللبيع</p>
|
<p className={'text-xs text-gray-500 '}>متاح للبيع</p>
|
||||||
<p className={'line-clamp-2'}>{ dataFormatter.booleanFormatter(item.is_active) }</p>
|
<p className={'line-clamp-2'}>{ dataFormatter.booleanFormatter(item.is_active) }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -185,7 +185,7 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
|
|||||||
))}
|
))}
|
||||||
{!loading && products.length === 0 && (
|
{!loading && products.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -277,7 +277,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">التصفية</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -301,7 +301,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">
|
||||||
Value
|
القيمة
|
||||||
</div>
|
</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
@ -311,7 +311,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">Select القيمة</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +326,22 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">من</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder='من'
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">إلى</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder='إلى'
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -356,12 +356,12 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>
|
||||||
From
|
من
|
||||||
</div>
|
</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder='من'
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +369,11 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>إلى</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder='إلى'
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +383,11 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">يحتوي على</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder='اكتب قيمة البحث'
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +395,12 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">الإجراء</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label='حذف'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +413,13 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label='تطبيق'
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label='إلغاء'
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +429,14 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title="تأكيد العملية"
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? 'جارٍ الحذف...' : 'تأكيد'}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>هل أنت متأكد أنك تريد حذف هذا العنصر؟</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +450,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`حذف ${selectedRows.length === 1 ? 'سطر' : 'سطور'}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -87,7 +87,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'product_name',
|
field: 'product_name',
|
||||||
headerName: 'اسمالمنتج',
|
headerName: 'اسم المنتج',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -102,7 +102,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'sku',
|
field: 'sku',
|
||||||
headerName: 'رمزالمنتج',
|
headerName: 'رمز المنتج',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -132,7 +132,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'cost_price',
|
field: 'cost_price',
|
||||||
headerName: 'سعرالتكلفةالحقيقي',
|
headerName: 'سعر التكلفة',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -148,7 +148,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'sale_price',
|
field: 'sale_price',
|
||||||
headerName: 'سعرالبيع',
|
headerName: 'سعر البيع',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -164,7 +164,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'sale_price_backup',
|
field: 'sale_price_backup',
|
||||||
headerName: 'سعرالبيعالسابق',
|
headerName: 'سعر البيع السابق',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -180,7 +180,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'cost_price_backup',
|
field: 'cost_price_backup',
|
||||||
headerName: 'سعرالتكلفةالسابق',
|
headerName: 'سعر التكلفة السابق',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -196,7 +196,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'usd_price',
|
field: 'usd_price',
|
||||||
headerName: 'السعربالدولار',
|
headerName: 'السعر بالدولار',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -212,7 +212,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'stock_quantity',
|
field: 'stock_quantity',
|
||||||
headerName: 'الكميةبالمخزون',
|
headerName: 'الكمية بالمخزون',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -228,7 +228,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'low_stock_threshold',
|
field: 'low_stock_threshold',
|
||||||
headerName: 'حدالتنبيهلنقصالمخزون',
|
headerName: 'حد التنبيه لنقص المخزون',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -244,7 +244,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'product_images',
|
field: 'product_images',
|
||||||
headerName: 'صورالمنتج',
|
headerName: 'صور المنتج',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -265,7 +265,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'is_active',
|
field: 'is_active',
|
||||||
headerName: 'متاحللبيع',
|
headerName: 'متاح للبيع',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
|
|||||||
@ -117,7 +117,7 @@ const CardRoles = ({
|
|||||||
))}
|
))}
|
||||||
{!loading && roles.length === 0 && (
|
{!loading && roles.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -86,7 +86,7 @@ const ListRoles = ({ roles, loading, onDelete, currentPage, numPages, onPageChan
|
|||||||
))}
|
))}
|
||||||
{!loading && roles.length === 0 && (
|
{!loading && roles.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -395,12 +395,12 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">إجراء</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label='حذف'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +413,13 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label='تطبيق'
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label='إلغاء'
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +429,14 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title="تأكيد العملية"
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? 'جارٍ الحذف...' : 'تأكيد'}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>هل أنت متأكد من حذف هذا العنصر؟</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +450,7 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`حذف ${selectedRows.length === 1 ? 'السجل المحدد' : 'السجلات المحددة'}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -126,7 +126,7 @@ const CardSales_invoice_items = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>سعرالبيعوقتالبيع</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>سعر البيعوقتالبيع</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.sale_price_snapshot }
|
{ item.sale_price_snapshot }
|
||||||
@ -177,7 +177,7 @@ const CardSales_invoice_items = ({
|
|||||||
))}
|
))}
|
||||||
{!loading && sales_invoice_items.length === 0 && (
|
{!loading && sales_invoice_items.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -80,7 +80,7 @@ const ListSales_invoice_items = ({ sales_invoice_items, loading, onDelete, curre
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>سعرالبيعوقتالبيع</p>
|
<p className={'text-xs text-gray-500 '}>سعر البيعوقتالبيع</p>
|
||||||
<p className={'line-clamp-2'}>{ item.sale_price_snapshot }</p>
|
<p className={'line-clamp-2'}>{ item.sale_price_snapshot }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ const ListSales_invoice_items = ({ sales_invoice_items, loading, onDelete, curre
|
|||||||
))}
|
))}
|
||||||
{!loading && sales_invoice_items.length === 0 && (
|
{!loading && sales_invoice_items.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -395,12 +395,12 @@ const TableSampleSales_invoice_items = ({ filterItems, setFilterItems, filters,
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">إجراء</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label='حذف'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +413,13 @@ const TableSampleSales_invoice_items = ({ filterItems, setFilterItems, filters,
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label='تطبيق'
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label='إلغاء'
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +429,14 @@ const TableSampleSales_invoice_items = ({ filterItems, setFilterItems, filters,
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title="تأكيد العملية"
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? 'جارٍ الحذف...' : 'تأكيد'}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>هل أنت متأكد من حذف هذا العنصر؟</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +450,7 @@ const TableSampleSales_invoice_items = ({ filterItems, setFilterItems, filters,
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`حذف ${selectedRows.length === 1 ? 'السجل المحدد' : 'السجلات المحددة'}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -118,7 +118,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'sale_price_snapshot',
|
field: 'sale_price_snapshot',
|
||||||
headerName: 'سعرالبيعوقتالبيع',
|
headerName: 'سعر البيعوقتالبيع',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
|
|||||||
@ -7,6 +7,10 @@ import { Pagination } from '../Pagination';
|
|||||||
import {saveFile} from "../../helpers/fileSaver";
|
import {saveFile} from "../../helpers/fileSaver";
|
||||||
import LoadingSpinner from "../LoadingSpinner";
|
import LoadingSpinner from "../LoadingSpinner";
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import {
|
||||||
|
formatSalesInvoicePaymentMethod,
|
||||||
|
formatSalesInvoiceStatus,
|
||||||
|
} from '../../helpers/salesInvoiceLabels';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
@ -102,7 +106,7 @@ const CardSales_invoices = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>رقمالفاتورة</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>رقم الفاتورة</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.invoice_number }
|
{ item.invoice_number }
|
||||||
@ -114,7 +118,7 @@ const CardSales_invoices = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>تاريخووقتالبيع</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>تاريخ ووقت البيع</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ dataFormatter.dateTimeFormatter(item.sold_at) }
|
{ dataFormatter.dateTimeFormatter(item.sold_at) }
|
||||||
@ -129,7 +133,7 @@ const CardSales_invoices = ({
|
|||||||
<dt className=' text-gray-500 dark:text-dark-600'>الحالة</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>الحالة</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.status }
|
{ formatSalesInvoiceStatus(item.status) }
|
||||||
</div>
|
</div>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
@ -138,7 +142,7 @@ const CardSales_invoices = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>الإجماليقبلالخصم</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>الإجمالي قبل الخصم</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.subtotal_amount }
|
{ item.subtotal_amount }
|
||||||
@ -162,7 +166,7 @@ const CardSales_invoices = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>الإجماليالنهائي</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>الإجمالي النهائي</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.total_amount }
|
{ item.total_amount }
|
||||||
@ -174,7 +178,7 @@ const CardSales_invoices = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>إجماليالتكلفة</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>إجمالي التكلفة</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.total_cost_amount }
|
{ item.total_cost_amount }
|
||||||
@ -186,7 +190,7 @@ const CardSales_invoices = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>إجماليالربح</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>إجمالي الربح</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.total_profit_amount }
|
{ item.total_profit_amount }
|
||||||
@ -198,10 +202,10 @@ const CardSales_invoices = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>طريقةالدفع</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>طريقة الدفع</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.payment_method }
|
{ formatSalesInvoicePaymentMethod(item.payment_method) }
|
||||||
</div>
|
</div>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
@ -222,7 +226,7 @@ const CardSales_invoices = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>بنودالفاتورة</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>بنود الفاتورة</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ dataFormatter.sales_invoice_itemsManyListFormatter(item.items).join(', ')}
|
{ dataFormatter.sales_invoice_itemsManyListFormatter(item.items).join(', ')}
|
||||||
@ -237,7 +241,7 @@ const CardSales_invoices = ({
|
|||||||
))}
|
))}
|
||||||
{!loading && sales_invoices.length === 0 && (
|
{!loading && sales_invoices.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -8,6 +8,10 @@ import {useAppSelector} from "../../stores/hooks";
|
|||||||
import {Pagination} from "../Pagination";
|
import {Pagination} from "../Pagination";
|
||||||
import LoadingSpinner from "../LoadingSpinner";
|
import LoadingSpinner from "../LoadingSpinner";
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import {
|
||||||
|
formatSalesInvoicePaymentMethod,
|
||||||
|
formatSalesInvoiceStatus,
|
||||||
|
} from '../../helpers/salesInvoiceLabels';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
@ -64,7 +68,7 @@ const ListSales_invoices = ({ sales_invoices, loading, onDelete, currentPage, nu
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>رقمالفاتورة</p>
|
<p className={'text-xs text-gray-500 '}>رقم الفاتورة</p>
|
||||||
<p className={'line-clamp-2'}>{ item.invoice_number }</p>
|
<p className={'line-clamp-2'}>{ item.invoice_number }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -72,7 +76,7 @@ const ListSales_invoices = ({ sales_invoices, loading, onDelete, currentPage, nu
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>تاريخووقتالبيع</p>
|
<p className={'text-xs text-gray-500 '}>تاريخ ووقت البيع</p>
|
||||||
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.sold_at) }</p>
|
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.sold_at) }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -81,14 +85,14 @@ const ListSales_invoices = ({ sales_invoices, loading, onDelete, currentPage, nu
|
|||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>الحالة</p>
|
<p className={'text-xs text-gray-500 '}>الحالة</p>
|
||||||
<p className={'line-clamp-2'}>{ item.status }</p>
|
<p className={'line-clamp-2'}>{ formatSalesInvoiceStatus(item.status) }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>الإجماليقبلالخصم</p>
|
<p className={'text-xs text-gray-500 '}>الإجمالي قبل الخصم</p>
|
||||||
<p className={'line-clamp-2'}>{ item.subtotal_amount }</p>
|
<p className={'line-clamp-2'}>{ item.subtotal_amount }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -104,7 +108,7 @@ const ListSales_invoices = ({ sales_invoices, loading, onDelete, currentPage, nu
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>الإجماليالنهائي</p>
|
<p className={'text-xs text-gray-500 '}>الإجمالي النهائي</p>
|
||||||
<p className={'line-clamp-2'}>{ item.total_amount }</p>
|
<p className={'line-clamp-2'}>{ item.total_amount }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -112,7 +116,7 @@ const ListSales_invoices = ({ sales_invoices, loading, onDelete, currentPage, nu
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>إجماليالتكلفة</p>
|
<p className={'text-xs text-gray-500 '}>إجمالي التكلفة</p>
|
||||||
<p className={'line-clamp-2'}>{ item.total_cost_amount }</p>
|
<p className={'line-clamp-2'}>{ item.total_cost_amount }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -120,7 +124,7 @@ const ListSales_invoices = ({ sales_invoices, loading, onDelete, currentPage, nu
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>إجماليالربح</p>
|
<p className={'text-xs text-gray-500 '}>إجمالي الربح</p>
|
||||||
<p className={'line-clamp-2'}>{ item.total_profit_amount }</p>
|
<p className={'line-clamp-2'}>{ item.total_profit_amount }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -128,8 +132,8 @@ const ListSales_invoices = ({ sales_invoices, loading, onDelete, currentPage, nu
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>طريقةالدفع</p>
|
<p className={'text-xs text-gray-500 '}>طريقة الدفع</p>
|
||||||
<p className={'line-clamp-2'}>{ item.payment_method }</p>
|
<p className={'line-clamp-2'}>{ formatSalesInvoicePaymentMethod(item.payment_method) }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -144,7 +148,7 @@ const ListSales_invoices = ({ sales_invoices, loading, onDelete, currentPage, nu
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>بنودالفاتورة</p>
|
<p className={'text-xs text-gray-500 '}>بنود الفاتورة</p>
|
||||||
<p className={'line-clamp-2'}>{ dataFormatter.sales_invoice_itemsManyListFormatter(item.items).join(', ')}</p>
|
<p className={'line-clamp-2'}>{ dataFormatter.sales_invoice_itemsManyListFormatter(item.items).join(', ')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -166,7 +170,7 @@ const ListSales_invoices = ({ sales_invoices, loading, onDelete, currentPage, nu
|
|||||||
))}
|
))}
|
||||||
{!loading && sales_invoices.length === 0 && (
|
{!loading && sales_invoices.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -20,10 +20,20 @@ import {dataGridStyles} from "../../styles";
|
|||||||
|
|
||||||
import KanbanBoard from '../KanbanBoard/KanbanBoard';
|
import KanbanBoard from '../KanbanBoard/KanbanBoard';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import {
|
||||||
|
formatSalesInvoicePaymentMethod,
|
||||||
|
formatSalesInvoiceStatus,
|
||||||
|
} from '../../helpers/salesInvoiceLabels';
|
||||||
|
|
||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
|
|
||||||
|
const getEnumOptionLabel = (fieldName: string, value: string) => {
|
||||||
|
if (fieldName === 'status') return formatSalesInvoiceStatus(value);
|
||||||
|
if (fieldName === 'payment_method') return formatSalesInvoicePaymentMethod(value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
const TableSampleSales_invoices = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleSales_invoices = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
@ -98,13 +108,13 @@ const TableSampleSales_invoices = ({ filterItems, setFilterItems, filters, showG
|
|||||||
|
|
||||||
setKanbanColumns([
|
setKanbanColumns([
|
||||||
|
|
||||||
{ id: "draft", label: "draft" },
|
{ id: "draft", label: "مسودة" },
|
||||||
|
|
||||||
{ id: "paid", label: "paid" },
|
{ id: "paid", label: "مدفوعة" },
|
||||||
|
|
||||||
{ id: "voided", label: "voided" },
|
{ id: "voided", label: "ملغاة" },
|
||||||
|
|
||||||
{ id: "refunded", label: "refunded" },
|
{ id: "refunded", label: "مسترجعة" },
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -295,7 +305,7 @@ const TableSampleSales_invoices = ({ filterItems, setFilterItems, filters, showG
|
|||||||
{filterItems && Array.isArray( filterItems ) && filterItems.length ?
|
{filterItems && Array.isArray( filterItems ) && filterItems.length ?
|
||||||
<CardBox>
|
<CardBox>
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{
|
initialالقيمةs={{
|
||||||
checkboxes: ['lorem'],
|
checkboxes: ['lorem'],
|
||||||
switches: ['lorem'],
|
switches: ['lorem'],
|
||||||
radio: 'lorem',
|
radio: 'lorem',
|
||||||
@ -308,7 +318,7 @@ const TableSampleSales_invoices = ({ filterItems, setFilterItems, filters, showG
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">التصفية</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -332,7 +342,7 @@ const TableSampleSales_invoices = ({ filterItems, setFilterItems, filters, showG
|
|||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">
|
||||||
Value
|
القيمة
|
||||||
</div>
|
</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
@ -342,12 +352,12 @@ const TableSampleSales_invoices = ({ filterItems, setFilterItems, filters, showG
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">اختر القيمة</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
<option key={option} value={option}>
|
<option key={option} value={option}>
|
||||||
{option}
|
{getEnumOptionLabel(filterItem?.fields?.selectedField, option)}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</Field>
|
</Field>
|
||||||
@ -357,22 +367,22 @@ const TableSampleSales_invoices = ({ filterItems, setFilterItems, filters, showG
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">من</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder='من'
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">إلى</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder='إلى'
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -387,12 +397,12 @@ const TableSampleSales_invoices = ({ filterItems, setFilterItems, filters, showG
|
|||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>
|
||||||
From
|
من
|
||||||
</div>
|
</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder='من'
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -400,11 +410,11 @@ const TableSampleSales_invoices = ({ filterItems, setFilterItems, filters, showG
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>إلى</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder='إلى'
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -414,11 +424,11 @@ const TableSampleSales_invoices = ({ filterItems, setFilterItems, filters, showG
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">يحتوي على</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder='أدخل القيمة'
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -426,12 +436,12 @@ const TableSampleSales_invoices = ({ filterItems, setFilterItems, filters, showG
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">الإجراء</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label='حذف'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -444,13 +454,13 @@ const TableSampleSales_invoices = ({ filterItems, setFilterItems, filters, showG
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label='تطبيق'
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label='إلغاء'
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -460,14 +470,14 @@ const TableSampleSales_invoices = ({ filterItems, setFilterItems, filters, showG
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title="تأكيد العملية"
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? 'جارٍ الحذف...' : 'تأكيد'}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>هل أنت متأكد أنك تريد حذف هذا العنصر؟</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -494,7 +504,7 @@ const TableSampleSales_invoices = ({ filterItems, setFilterItems, filters, showG
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`حذف ${selectedRows.length === 1 ? 'صف واحد' : 'عدة صفوف'}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -12,6 +12,10 @@ import {saveFile} from "../../helpers/fileSaver";
|
|||||||
import dataFormatter from '../../helpers/dataFormatter'
|
import dataFormatter from '../../helpers/dataFormatter'
|
||||||
import DataGridMultiSelect from "../DataGridMultiSelect";
|
import DataGridMultiSelect from "../DataGridMultiSelect";
|
||||||
import ListActionsPopover from '../ListActionsPopover';
|
import ListActionsPopover from '../ListActionsPopover';
|
||||||
|
import {
|
||||||
|
formatSalesInvoicePaymentMethod,
|
||||||
|
formatSalesInvoiceStatus,
|
||||||
|
} from '../../helpers/salesInvoiceLabels';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
@ -87,7 +91,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'invoice_number',
|
field: 'invoice_number',
|
||||||
headerName: 'رقمالفاتورة',
|
headerName: 'رقم الفاتورة',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -102,7 +106,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'sold_at',
|
field: 'sold_at',
|
||||||
headerName: 'تاريخووقتالبيع',
|
headerName: 'تاريخ ووقت البيع',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -129,13 +133,13 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
|
|
||||||
editable: hasUpdatePermission,
|
editable: hasUpdatePermission,
|
||||||
|
valueFormatter: ({ value }) => formatSalesInvoiceStatus(value),
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
field: 'subtotal_amount',
|
field: 'subtotal_amount',
|
||||||
headerName: 'الإجماليقبلالخصم',
|
headerName: 'الإجمالي قبل الخصم',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -167,7 +171,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'total_amount',
|
field: 'total_amount',
|
||||||
headerName: 'الإجماليالنهائي',
|
headerName: 'الإجمالي النهائي',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -183,7 +187,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'total_cost_amount',
|
field: 'total_cost_amount',
|
||||||
headerName: 'إجماليالتكلفة',
|
headerName: 'إجمالي التكلفة',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -199,7 +203,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'total_profit_amount',
|
field: 'total_profit_amount',
|
||||||
headerName: 'إجماليالربح',
|
headerName: 'إجمالي الربح',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -215,7 +219,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'payment_method',
|
field: 'payment_method',
|
||||||
headerName: 'طريقةالدفع',
|
headerName: 'طريقة الدفع',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -224,7 +228,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
|
|
||||||
editable: hasUpdatePermission,
|
editable: hasUpdatePermission,
|
||||||
|
valueFormatter: ({ value }) => formatSalesInvoicePaymentMethod(value),
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -245,7 +249,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'items',
|
field: 'items',
|
||||||
headerName: 'بنودالفاتورة',
|
headerName: 'بنود الفاتورة',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
|
|||||||
@ -11,9 +11,9 @@ const Search = () => {
|
|||||||
const validateSearch = (value) => {
|
const validateSearch = (value) => {
|
||||||
let error;
|
let error;
|
||||||
if (!value) {
|
if (!value) {
|
||||||
error = 'Required';
|
error = 'الرجاء كتابة كلمة للبحث';
|
||||||
} else if (value.length < 2) {
|
} else if (value.length < 2) {
|
||||||
error = 'Minimum length: 2 characters';
|
error = 'الحد الأدنى حرفان';
|
||||||
}
|
}
|
||||||
return error;
|
return error;
|
||||||
};
|
};
|
||||||
@ -31,13 +31,13 @@ const Search = () => {
|
|||||||
validateOnChange={false}
|
validateOnChange={false}
|
||||||
>
|
>
|
||||||
{({ errors, touched, values }) => (
|
{({ errors, touched, values }) => (
|
||||||
<Form style={{width: '300px'}} >
|
<Form className='w-[220px] sm:w-[300px]'>
|
||||||
<Field
|
<Field
|
||||||
id='search'
|
id='search'
|
||||||
name='search'
|
name='search'
|
||||||
validate={validateSearch}
|
validate={validateSearch}
|
||||||
placeholder='Search'
|
placeholder='ابحث عن صفحة أو عنصر' dir='rtl'
|
||||||
className={` ${corners} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-2 relative ml-2 w-full dark:placeholder-dark-600 ${focusRing} shadow-none`}
|
className={`${corners} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 relative ml-2 w-full p-2.5 text-sm font-medium shadow-none transition-all duration-200 ease-out placeholder:font-normal dark:placeholder-dark-600 ${focusRing}`}
|
||||||
/>
|
/>
|
||||||
{errors.search && touched.search && values.search.length < 2 ? (
|
{errors.search && touched.search && values.search.length < 2 ? (
|
||||||
<div className='text-red-500 text-sm ml-2 absolute'>{errors.search}</div>
|
<div className='text-red-500 text-sm ml-2 absolute'>{errors.search}</div>
|
||||||
|
|||||||
@ -78,7 +78,7 @@ const CardShops = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>اسمالمتجر</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>اسم المحل</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.shop_name }
|
{ item.shop_name }
|
||||||
@ -90,7 +90,7 @@ const CardShops = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>اسمالمالك</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>اسم المالك</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.owner_name }
|
{ item.owner_name }
|
||||||
@ -102,7 +102,7 @@ const CardShops = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>هاتفالمتجر</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>هاتف المحل</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.phone }
|
{ item.phone }
|
||||||
@ -126,7 +126,7 @@ const CardShops = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>اسمالعملةالمحلية</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>اسم العملة المحلية</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.currency_name }
|
{ item.currency_name }
|
||||||
@ -138,7 +138,7 @@ const CardShops = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>سعرالدولاراليومي</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>سعر الدولار اليومي</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ item.usd_rate }
|
{ item.usd_rate }
|
||||||
@ -150,7 +150,7 @@ const CardShops = ({
|
|||||||
|
|
||||||
|
|
||||||
<div className='flex justify-between gap-x-4 py-3'>
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
<dt className=' text-gray-500 dark:text-dark-600'>السماحبمخزونسالب</dt>
|
<dt className=' text-gray-500 dark:text-dark-600'>السماح بمخزون سالب</dt>
|
||||||
<dd className='flex items-start gap-x-2'>
|
<dd className='flex items-start gap-x-2'>
|
||||||
<div className='font-medium line-clamp-4'>
|
<div className='font-medium line-clamp-4'>
|
||||||
{ dataFormatter.booleanFormatter(item.allow_negative_stock) }
|
{ dataFormatter.booleanFormatter(item.allow_negative_stock) }
|
||||||
@ -177,7 +177,7 @@ const CardShops = ({
|
|||||||
))}
|
))}
|
||||||
{!loading && shops.length === 0 && (
|
{!loading && shops.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -48,7 +48,7 @@ const ListShops = ({ shops, loading, onDelete, currentPage, numPages, onPageChan
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>اسمالمتجر</p>
|
<p className={'text-xs text-gray-500 '}>اسم المحل</p>
|
||||||
<p className={'line-clamp-2'}>{ item.shop_name }</p>
|
<p className={'line-clamp-2'}>{ item.shop_name }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ const ListShops = ({ shops, loading, onDelete, currentPage, numPages, onPageChan
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>اسمالمالك</p>
|
<p className={'text-xs text-gray-500 '}>اسم المالك</p>
|
||||||
<p className={'line-clamp-2'}>{ item.owner_name }</p>
|
<p className={'line-clamp-2'}>{ item.owner_name }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ const ListShops = ({ shops, loading, onDelete, currentPage, numPages, onPageChan
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>هاتفالمتجر</p>
|
<p className={'text-xs text-gray-500 '}>هاتف المحل</p>
|
||||||
<p className={'line-clamp-2'}>{ item.phone }</p>
|
<p className={'line-clamp-2'}>{ item.phone }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ const ListShops = ({ shops, loading, onDelete, currentPage, numPages, onPageChan
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>اسمالعملةالمحلية</p>
|
<p className={'text-xs text-gray-500 '}>اسم العملة المحلية</p>
|
||||||
<p className={'line-clamp-2'}>{ item.currency_name }</p>
|
<p className={'line-clamp-2'}>{ item.currency_name }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ const ListShops = ({ shops, loading, onDelete, currentPage, numPages, onPageChan
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>سعرالدولاراليومي</p>
|
<p className={'text-xs text-gray-500 '}>سعر الدولار اليومي</p>
|
||||||
<p className={'line-clamp-2'}>{ item.usd_rate }</p>
|
<p className={'line-clamp-2'}>{ item.usd_rate }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ const ListShops = ({ shops, loading, onDelete, currentPage, numPages, onPageChan
|
|||||||
|
|
||||||
|
|
||||||
<div className={'flex-1 px-3'}>
|
<div className={'flex-1 px-3'}>
|
||||||
<p className={'text-xs text-gray-500 '}>السماحبمخزونسالب</p>
|
<p className={'text-xs text-gray-500 '}>السماح بمخزون سالب</p>
|
||||||
<p className={'line-clamp-2'}>{ dataFormatter.booleanFormatter(item.allow_negative_stock) }</p>
|
<p className={'line-clamp-2'}>{ dataFormatter.booleanFormatter(item.allow_negative_stock) }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ const ListShops = ({ shops, loading, onDelete, currentPage, numPages, onPageChan
|
|||||||
))}
|
))}
|
||||||
{!loading && shops.length === 0 && (
|
{!loading && shops.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -279,7 +279,7 @@ const TableSampleShops = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">التصفية</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -313,7 +313,7 @@ const TableSampleShops = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">اختر قيمة</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -328,22 +328,22 @@ const TableSampleShops = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">من</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder='من'
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">إلى</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder='إلى'
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -363,7 +363,7 @@ const TableSampleShops = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder='من'
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -371,11 +371,11 @@ const TableSampleShops = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>إلى</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder='إلى'
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -385,11 +385,11 @@ const TableSampleShops = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">يحتوي على</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder='يحتوي على'
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -397,12 +397,12 @@ const TableSampleShops = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">الإجراء</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label='حذف'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -415,13 +415,13 @@ const TableSampleShops = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label='تطبيق'
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label='إلغاء'
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -431,14 +431,14 @@ const TableSampleShops = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title="تأكيد العملية"
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? 'جارٍ الحذف...' : 'تأكيد'}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>هل أنت متأكد أنك تريد حذف هذا العنصر؟</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -463,7 +463,7 @@ const TableSampleShops = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`حذف ${selectedRows.length === 1 ? 'صف واحد' : 'عدة صفوف'}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -43,7 +43,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'shop_name',
|
field: 'shop_name',
|
||||||
headerName: 'اسمالمتجر',
|
headerName: 'اسم المحل',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -58,7 +58,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'owner_name',
|
field: 'owner_name',
|
||||||
headerName: 'اسمالمالك',
|
headerName: 'اسم المالك',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -73,7 +73,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'phone',
|
field: 'phone',
|
||||||
headerName: 'هاتفالمتجر',
|
headerName: 'هاتف المحل',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -103,7 +103,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'currency_name',
|
field: 'currency_name',
|
||||||
headerName: 'اسمالعملةالمحلية',
|
headerName: 'اسم العملة المحلية',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -118,7 +118,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'usd_rate',
|
field: 'usd_rate',
|
||||||
headerName: 'سعرالدولاراليومي',
|
headerName: 'سعر الدولار اليومي',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
@ -134,7 +134,7 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'allow_negative_stock',
|
field: 'allow_negative_stock',
|
||||||
headerName: 'السماحبمخزونسالب',
|
headerName: 'السماح بمخزون سالب',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
|
|||||||
@ -202,7 +202,7 @@ const CardUsers = ({
|
|||||||
))}
|
))}
|
||||||
{!loading && users.length === 0 && (
|
{!loading && users.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -145,7 +145,7 @@ const ListUsers = ({ users, loading, onDelete, currentPage, numPages, onPageChan
|
|||||||
))}
|
))}
|
||||||
{!loading && users.length === 0 && (
|
{!loading && users.length === 0 && (
|
||||||
<div className='col-span-full flex items-center justify-center h-40'>
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
<p className=''>No data to display</p>
|
<p className=''>لا توجد بيانات للعرض</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -395,12 +395,12 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">إجراء</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label='حذف'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +413,13 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label='تطبيق'
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label='إلغاء'
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +429,14 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title="تأكيد العملية"
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? 'جارٍ الحذف...' : 'تأكيد'}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>هل أنت متأكد من حذف هذا العنصر؟</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +450,7 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`حذف ${selectedRows.length === 1 ? 'السجل المحدد' : 'السجلات المحددة'}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700;800&display=swap');
|
||||||
@import "tailwind/_base.css";
|
@import "tailwind/_base.css";
|
||||||
@import "tailwind/_components.css";
|
@import "tailwind/_components.css";
|
||||||
@import "tailwind/_utilities.css";
|
@import "tailwind/_utilities.css";
|
||||||
@ -33,3 +34,69 @@
|
|||||||
.introjs-prevbutton{
|
.introjs-prevbutton{
|
||||||
@apply bg-transparent border border-blue-600 text-blue-600 !important;
|
@apply bg-transparent border border-blue-600 text-blue-600 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--app-font-primary: 'Tajawal', 'Segoe UI', Tahoma, Arial, sans-serif;
|
||||||
|
--app-transition-fast: 180ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
--app-transition-base: 260ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
font-family: var(--app-font-primary);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
transition:
|
||||||
|
color var(--app-transition-fast),
|
||||||
|
background-color var(--app-transition-fast),
|
||||||
|
border-color var(--app-transition-fast),
|
||||||
|
box-shadow var(--app-transition-fast),
|
||||||
|
opacity var(--app-transition-fast),
|
||||||
|
transform var(--app-transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-rtl {
|
||||||
|
direction: rtl;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-rtl input,
|
||||||
|
.app-rtl textarea,
|
||||||
|
.app-rtl select {
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-rtl .ltr-chip {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|||||||
23
frontend/src/helpers/salesInvoiceLabels.ts
Normal file
23
frontend/src/helpers/salesInvoiceLabels.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export const SALES_INVOICE_STATUS_LABELS: Record<string, string> = {
|
||||||
|
draft: 'مسودة',
|
||||||
|
paid: 'مدفوعة',
|
||||||
|
voided: 'ملغاة',
|
||||||
|
refunded: 'مسترجعة',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SALES_INVOICE_PAYMENT_METHOD_LABELS: Record<string, string> = {
|
||||||
|
cash: 'نقداً',
|
||||||
|
card: 'بطاقة',
|
||||||
|
transfer: 'تحويل',
|
||||||
|
mixed: 'مختلط',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatSalesInvoiceStatus = (value?: string | null) => {
|
||||||
|
if (!value) return 'لا توجد بيانات';
|
||||||
|
return SALES_INVOICE_STATUS_LABELS[value] ?? value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatSalesInvoicePaymentMethod = (value?: string | null) => {
|
||||||
|
if (!value) return 'لا توجد بيانات';
|
||||||
|
return SALES_INVOICE_PAYMENT_METHOD_LABELS[value] ?? value;
|
||||||
|
};
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import React, { ReactNode, useEffect } from 'react'
|
import React, { ReactNode, useEffect, useState } from 'react'
|
||||||
import { useState } from 'react'
|
import jwt from 'jsonwebtoken'
|
||||||
import jwt from 'jsonwebtoken';
|
|
||||||
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
|
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
|
||||||
import menuAside from '../menuAside'
|
import menuAside from '../menuAside'
|
||||||
import menuNavBar from '../menuNavBar'
|
import menuNavBar from '../menuNavBar'
|
||||||
@ -10,11 +9,11 @@ import NavBarItemPlain from '../components/NavBarItemPlain'
|
|||||||
import AsideMenu from '../components/AsideMenu'
|
import AsideMenu from '../components/AsideMenu'
|
||||||
import FooterBar from '../components/FooterBar'
|
import FooterBar from '../components/FooterBar'
|
||||||
import { useAppDispatch, useAppSelector } from '../stores/hooks'
|
import { useAppDispatch, useAppSelector } from '../stores/hooks'
|
||||||
import Search from '../components/Search';
|
import Search from '../components/Search'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import {findMe, logoutUser} from "../stores/authSlice";
|
import { findMe, logoutUser } from '../stores/authSlice'
|
||||||
|
|
||||||
import {hasPermission} from "../helpers/userPermissions";
|
import { hasPermission } from '../helpers/userPermissions'
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -122,7 +121,7 @@ export default function LayoutAuthenticated({
|
|||||||
onAsideLgClose={() => setIsAsideLgActive(false)}
|
onAsideLgClose={() => setIsAsideLgActive(false)}
|
||||||
/>
|
/>
|
||||||
{children}
|
{children}
|
||||||
<FooterBar>Hand-crafted & Made with ❤️</FooterBar>
|
<FooterBar>واجهة مبسطة لإدارة المحل</FooterBar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -5,102 +5,53 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/dashboard',
|
href: '/dashboard',
|
||||||
icon: icon.mdiViewDashboardOutline,
|
icon: icon.mdiViewDashboardOutline,
|
||||||
label: 'Dashboard',
|
label: 'الصفحة الرئيسية',
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
href: '/users/users-list',
|
|
||||||
label: 'Users',
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
icon: icon.mdiAccountGroup ?? icon.mdiTable,
|
|
||||||
permissions: 'READ_USERS'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: '/roles/roles-list',
|
href: '/cashier',
|
||||||
label: 'Roles',
|
label: 'الكاشير',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable,
|
icon: 'mdiCashRegister' in icon ? icon['mdiCashRegister' as keyof typeof icon] : ('mdiReceipt' in icon ? icon['mdiReceipt' as keyof typeof icon] : icon.mdiTable),
|
||||||
permissions: 'READ_ROLES'
|
permissions: 'READ_PRODUCTS',
|
||||||
},
|
|
||||||
{
|
|
||||||
href: '/permissions/permissions-list',
|
|
||||||
label: 'Permissions',
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
icon: icon.mdiShieldAccountOutline ?? icon.mdiTable,
|
|
||||||
permissions: 'READ_PERMISSIONS'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: '/organizations/organizations-list',
|
|
||||||
label: 'Organizations',
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
icon: icon.mdiTable ?? icon.mdiTable,
|
|
||||||
permissions: 'READ_ORGANIZATIONS'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: '/shops/shops-list',
|
|
||||||
label: 'Shops',
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
icon: 'mdiStorefront' in icon ? icon['mdiStorefront' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
|
||||||
permissions: 'READ_SHOPS'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: '/categories/categories-list',
|
|
||||||
label: 'Categories',
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
icon: 'mdiShape' in icon ? icon['mdiShape' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
|
||||||
permissions: 'READ_CATEGORIES'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: '/products/products-list',
|
href: '/products/products-list',
|
||||||
label: 'Products',
|
label: 'المنتجات',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiSprayBottle' in icon ? icon['mdiSprayBottle' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiSprayBottle' in icon ? icon['mdiSprayBottle' as keyof typeof icon] : icon.mdiTable,
|
||||||
permissions: 'READ_PRODUCTS'
|
permissions: 'READ_PRODUCTS',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/categories/categories-list',
|
||||||
|
label: 'الأقسام',
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
icon: 'mdiShape' in icon ? icon['mdiShape' as keyof typeof icon] : icon.mdiTable,
|
||||||
|
permissions: 'READ_CATEGORIES',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: '/sales_invoices/sales_invoices-list',
|
href: '/sales_invoices/sales_invoices-list',
|
||||||
label: 'Sales invoices',
|
label: 'الفواتير',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiReceipt' in icon ? icon['mdiReceipt' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiReceipt' in icon ? icon['mdiReceipt' as keyof typeof icon] : icon.mdiTable,
|
||||||
permissions: 'READ_SALES_INVOICES'
|
permissions: 'READ_SALES_INVOICES',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: '/sales_invoice_items/sales_invoice_items-list',
|
href: '/shops/shops-list',
|
||||||
label: 'Sales invoice items',
|
label: 'المحل',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiFormatListBulleted' in icon ? icon['mdiFormatListBulleted' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiStorefront' in icon ? icon['mdiStorefront' as keyof typeof icon] : icon.mdiTable,
|
||||||
permissions: 'READ_SALES_INVOICE_ITEMS'
|
permissions: 'READ_SHOPS',
|
||||||
},
|
|
||||||
{
|
|
||||||
href: '/price_change_logs/price_change_logs-list',
|
|
||||||
label: 'Price change logs',
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
icon: 'mdiCurrencyUsd' in icon ? icon['mdiCurrencyUsd' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
|
||||||
permissions: 'READ_PRICE_CHANGE_LOGS'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: '/profile',
|
href: '/profile',
|
||||||
label: 'Profile',
|
label: 'الملف الشخصي',
|
||||||
icon: icon.mdiAccountCircle,
|
icon: icon.mdiAccountCircle,
|
||||||
},
|
withDevider: true,
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
href: '/api-docs',
|
|
||||||
target: '_blank',
|
|
||||||
label: 'Swagger API',
|
|
||||||
icon: icon.mdiFileCode,
|
|
||||||
permissions: 'READ_API_DOCS'
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ const menuNavBar: MenuNavBarItem[] = [
|
|||||||
menu: [
|
menu: [
|
||||||
{
|
{
|
||||||
icon: mdiAccount,
|
icon: mdiAccount,
|
||||||
label: 'My Profile',
|
label: 'الملف الشخصي',
|
||||||
href: '/profile',
|
href: '/profile',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -27,20 +27,20 @@ const menuNavBar: MenuNavBarItem[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: mdiLogout,
|
icon: mdiLogout,
|
||||||
label: 'Log Out',
|
label: 'تسجيل الخروج',
|
||||||
isLogout: true,
|
isLogout: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: mdiThemeLightDark,
|
icon: mdiThemeLightDark,
|
||||||
label: 'Light/Dark',
|
label: 'الوضع الليلي',
|
||||||
isDesktopNoLabel: true,
|
isDesktopNoLabel: true,
|
||||||
isToggleLightDark: true,
|
isToggleLightDark: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: mdiLogout,
|
icon: mdiLogout,
|
||||||
label: 'Log out',
|
label: 'خروج',
|
||||||
isDesktopNoLabel: true,
|
isDesktopNoLabel: true,
|
||||||
isLogout: true,
|
isLogout: true,
|
||||||
},
|
},
|
||||||
|
|||||||
748
frontend/src/pages/cashier.tsx
Normal file
748
frontend/src/pages/cashier.tsx
Normal file
@ -0,0 +1,748 @@
|
|||||||
|
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||||
|
import Head from 'next/head';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import React, { ReactElement, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import BaseButton from '../components/BaseButton';
|
||||||
|
import CardBox from '../components/CardBox';
|
||||||
|
import LoadingSpinner from '../components/LoadingSpinner';
|
||||||
|
import SectionMain from '../components/SectionMain';
|
||||||
|
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
|
||||||
|
import { getPageTitle } from '../config';
|
||||||
|
import { formatSalesInvoicePaymentMethod } from '../helpers/salesInvoiceLabels';
|
||||||
|
import { hasPermission } from '../helpers/userPermissions';
|
||||||
|
import LayoutAuthenticated from '../layouts/Authenticated';
|
||||||
|
import { useAppSelector } from '../stores/hooks';
|
||||||
|
|
||||||
|
type CartItem = {
|
||||||
|
productId: string;
|
||||||
|
quantity: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type WorkspaceData = {
|
||||||
|
shops: any[];
|
||||||
|
selectedShop: any;
|
||||||
|
categories: any[];
|
||||||
|
products: any[];
|
||||||
|
summary: {
|
||||||
|
totalSales: number;
|
||||||
|
totalProfit: number;
|
||||||
|
invoiceCount: number;
|
||||||
|
};
|
||||||
|
recentInvoices: any[];
|
||||||
|
latestPriceChange: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatMoney = (value: number) => `${new Intl.NumberFormat('ar-IQ').format(value || 0)} د.ع`;
|
||||||
|
|
||||||
|
const formatUsd = (value: number | null) => {
|
||||||
|
if (value == null || Number.isNaN(value)) {
|
||||||
|
return '--';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${value.toFixed(2)} $`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDateTime = (value?: string | Date) => {
|
||||||
|
if (!value) {
|
||||||
|
return '--';
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(value).toLocaleString('ar-IQ', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialWorkspace: WorkspaceData = {
|
||||||
|
shops: [],
|
||||||
|
selectedShop: null,
|
||||||
|
categories: [],
|
||||||
|
products: [],
|
||||||
|
summary: {
|
||||||
|
totalSales: 0,
|
||||||
|
totalProfit: 0,
|
||||||
|
invoiceCount: 0,
|
||||||
|
},
|
||||||
|
recentInvoices: [],
|
||||||
|
latestPriceChange: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const CashierPage = () => {
|
||||||
|
const { currentUser } = useAppSelector((state) => state.auth);
|
||||||
|
const corners = useAppSelector((state) => state.style.corners);
|
||||||
|
const focusRing = useAppSelector((state) => state.style.focusRingColor);
|
||||||
|
|
||||||
|
const [workspace, setWorkspace] = useState<WorkspaceData>(initialWorkspace);
|
||||||
|
const [selectedShopId, setSelectedShopId] = useState('');
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
const [activeCategoryId, setActiveCategoryId] = useState('all');
|
||||||
|
const [cart, setCart] = useState<CartItem[]>([]);
|
||||||
|
const [paymentMethod, setPaymentMethod] = useState('cash');
|
||||||
|
const [notes, setNotes] = useState('');
|
||||||
|
const [usdRateInput, setUsdRateInput] = useState('');
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const [pricingBusy, setPricingBusy] = useState(false);
|
||||||
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
|
const [successInvoice, setSuccessInvoice] = useState<any>(null);
|
||||||
|
|
||||||
|
const canCheckout = Boolean(currentUser && hasPermission(currentUser, 'CREATE_SALES_INVOICES'));
|
||||||
|
const canManagePricing = Boolean(currentUser && hasPermission(currentUser, 'UPDATE_SHOPS'));
|
||||||
|
const canCreateProducts = Boolean(currentUser && hasPermission(currentUser, 'CREATE_PRODUCTS'));
|
||||||
|
const canCreateShops = Boolean(currentUser && hasPermission(currentUser, 'CREATE_SHOPS'));
|
||||||
|
|
||||||
|
const loadWorkspace = useCallback(
|
||||||
|
async (shopId?: string) => {
|
||||||
|
setLoading(true);
|
||||||
|
setErrorMessage('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get('/pos/workspace', {
|
||||||
|
params: shopId ? { shopId } : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
setWorkspace(data);
|
||||||
|
const resolvedShopId = shopId || data.selectedShop?.id || '';
|
||||||
|
setSelectedShopId(resolvedShopId);
|
||||||
|
setUsdRateInput(data.selectedShop?.usd_rate ? String(data.selectedShop.usd_rate) : '');
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('POS workspace load failed:', error);
|
||||||
|
setErrorMessage(error?.response?.data || 'تعذر تحميل شاشة الكاشير حالياً.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadWorkspace();
|
||||||
|
}, [loadWorkspace]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSuccessInvoice(null);
|
||||||
|
}, [selectedShopId]);
|
||||||
|
|
||||||
|
const productMap = useMemo(
|
||||||
|
() =>
|
||||||
|
new Map(
|
||||||
|
(workspace.products || []).map((product) => [product.id, product]),
|
||||||
|
),
|
||||||
|
[workspace.products],
|
||||||
|
);
|
||||||
|
|
||||||
|
const filteredProducts = useMemo(() => {
|
||||||
|
const normalizedQuery = query.trim().toLowerCase();
|
||||||
|
|
||||||
|
return (workspace.products || []).filter((product) => {
|
||||||
|
const matchesCategory = activeCategoryId === 'all' || product.categoryId === activeCategoryId;
|
||||||
|
const searchableText = [product.product_name, product.sku, product.barcode, product.category_name]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')
|
||||||
|
.toLowerCase();
|
||||||
|
const matchesQuery = !normalizedQuery || searchableText.includes(normalizedQuery);
|
||||||
|
|
||||||
|
return matchesCategory && matchesQuery;
|
||||||
|
});
|
||||||
|
}, [activeCategoryId, query, workspace.products]);
|
||||||
|
|
||||||
|
const suggestions = useMemo(() => filteredProducts.slice(0, 8), [filteredProducts]);
|
||||||
|
|
||||||
|
const cartDetails = useMemo(() => {
|
||||||
|
return cart
|
||||||
|
.map((item) => {
|
||||||
|
const product = productMap.get(item.productId);
|
||||||
|
if (!product) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineTotal = (product.sale_price || 0) * item.quantity;
|
||||||
|
const lineProfit = ((product.sale_price || 0) - (product.cost_price || 0)) * item.quantity;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
product,
|
||||||
|
lineTotal,
|
||||||
|
lineProfit,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean) as any[];
|
||||||
|
}, [cart, productMap]);
|
||||||
|
|
||||||
|
const cartSummary = useMemo(() => {
|
||||||
|
return cartDetails.reduce(
|
||||||
|
(acc, item) => ({
|
||||||
|
quantity: acc.quantity + item.quantity,
|
||||||
|
total: acc.total + item.lineTotal,
|
||||||
|
profit: acc.profit + item.lineProfit,
|
||||||
|
}),
|
||||||
|
{ quantity: 0, total: 0, profit: 0 },
|
||||||
|
);
|
||||||
|
}, [cartDetails]);
|
||||||
|
|
||||||
|
const addProductToCart = (productId: string) => {
|
||||||
|
setCart((current) => {
|
||||||
|
const existing = current.find((item) => item.productId === productId);
|
||||||
|
if (existing) {
|
||||||
|
return current.map((item) =>
|
||||||
|
item.productId === productId ? { ...item, quantity: item.quantity + 1 } : item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...current, { productId, quantity: 1 }];
|
||||||
|
});
|
||||||
|
setSuccessInvoice(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateCartQuantity = (productId: string, nextQuantity: number) => {
|
||||||
|
setCart((current) =>
|
||||||
|
current
|
||||||
|
.map((item) => (item.productId === productId ? { ...item, quantity: nextQuantity } : item))
|
||||||
|
.filter((item) => item.quantity > 0),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckout = async () => {
|
||||||
|
if (!selectedShopId || !cart.length) {
|
||||||
|
setErrorMessage('أضف منتجاً واحداً على الأقل قبل حفظ الفاتورة.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSubmitting(true);
|
||||||
|
setErrorMessage('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await axios.post('/pos/checkout', {
|
||||||
|
shopId: selectedShopId,
|
||||||
|
paymentMethod,
|
||||||
|
notes,
|
||||||
|
items: cart.map((item) => ({
|
||||||
|
productId: item.productId,
|
||||||
|
quantity: item.quantity,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
setSuccessInvoice(data);
|
||||||
|
setCart([]);
|
||||||
|
setNotes('');
|
||||||
|
setQuery('');
|
||||||
|
setActiveCategoryId('all');
|
||||||
|
await loadWorkspace(selectedShopId);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('POS checkout failed:', error);
|
||||||
|
setErrorMessage(error?.response?.data || 'حدث خطأ أثناء حفظ الفاتورة.');
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePricingAction = async (action: 'set_rate' | 'apply_prices' | 'restore_prices') => {
|
||||||
|
if (!selectedShopId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPricingBusy(true);
|
||||||
|
setErrorMessage('');
|
||||||
|
setSuccessInvoice(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload: Record<string, string> = {
|
||||||
|
shopId: selectedShopId,
|
||||||
|
action,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (action !== 'restore_prices') {
|
||||||
|
payload.usdRate = usdRateInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await axios.post('/pos/pricing', payload);
|
||||||
|
await loadWorkspace(selectedShopId);
|
||||||
|
setErrorMessage('');
|
||||||
|
setSuccessInvoice({
|
||||||
|
invoice_number: data.message,
|
||||||
|
total_amount: 0,
|
||||||
|
total_profit_amount: 0,
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('POS pricing action failed:', error);
|
||||||
|
setErrorMessage(error?.response?.data || 'تعذر تنفيذ تحديث الأسعار.');
|
||||||
|
} finally {
|
||||||
|
setPricingBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const emptyProductState = (
|
||||||
|
<CardBox className="border-dashed border-2 border-sky-100 bg-white/80">
|
||||||
|
<div className="space-y-3 py-6 text-center text-slate-600">
|
||||||
|
<p className="text-lg font-bold text-slate-900">لا توجد منتجات جاهزة للبيع بعد</p>
|
||||||
|
<p>ابدأ بإضافة منتجات وأقسام من لوحة الإدارة ليظهر الكاشير بشكل كامل.</p>
|
||||||
|
<div className="flex flex-wrap items-center justify-center gap-3">
|
||||||
|
{canCreateProducts && <BaseButton href="/products/products-new" color="success" label="إضافة منتج" />}
|
||||||
|
<BaseButton href="/products/products-list" color="info" label="عرض المنتجات" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>{getPageTitle('الكاشير')}</title>
|
||||||
|
</Head>
|
||||||
|
<SectionMain>
|
||||||
|
<div className="app-rtl space-y-6" dir="rtl">
|
||||||
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="شاشة الكاشير وتقارير اليوم" main>
|
||||||
|
{''}
|
||||||
|
</SectionTitleLineWithButton>
|
||||||
|
|
||||||
|
<CardBox className="overflow-hidden border-0 bg-gradient-to-l from-emerald-500 via-emerald-600 to-sky-500 text-white shadow-xl shadow-emerald-100/70">
|
||||||
|
<div className="grid gap-6 px-2 py-2 lg:grid-cols-[1.35fr,0.65fr] lg:items-center">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<span className="inline-flex items-center rounded-full bg-white/20 px-4 py-1 text-sm font-bold">
|
||||||
|
نظام بيع عربي سريع لمحل المنظفات
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-extrabold leading-tight lg:text-4xl">بيع أسرع، فواتير أوضح، وربح يومي محسوب بدقة</h1>
|
||||||
|
<p className="mt-3 max-w-3xl text-base text-emerald-50 lg:text-lg">
|
||||||
|
ابحث عن المنتج فوراً، أضفه للفاتورة بضغطة واحدة، وراقب المبيعات والأرباح اليومية مع تحديث أسعار الدولار من نفس الشاشة.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-3 sm:grid-cols-3 lg:grid-cols-1">
|
||||||
|
<div className="rounded-2xl bg-white/14 p-4 backdrop-blur-sm">
|
||||||
|
<div className="text-sm text-emerald-50">مبيعات اليوم</div>
|
||||||
|
<div className="mt-2 text-2xl font-extrabold">{formatMoney(workspace.summary.totalSales)}</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-2xl bg-white/14 p-4 backdrop-blur-sm">
|
||||||
|
<div className="text-sm text-emerald-50">أرباح اليوم</div>
|
||||||
|
<div className="mt-2 text-2xl font-extrabold">{formatMoney(workspace.summary.totalProfit)}</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-2xl bg-white/14 p-4 backdrop-blur-sm">
|
||||||
|
<div className="text-sm text-emerald-50">عدد الفواتير</div>
|
||||||
|
<div className="mt-2 text-2xl font-extrabold">{workspace.summary.invoiceCount}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
|
||||||
|
{errorMessage ? (
|
||||||
|
<div className="rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{errorMessage}</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{successInvoice ? (
|
||||||
|
<div className="rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-4 text-sm text-emerald-800">
|
||||||
|
<div className="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="font-bold text-emerald-900">تمت العملية بنجاح</p>
|
||||||
|
<p className="mt-1">
|
||||||
|
{successInvoice.id ? `تم إنشاء الفاتورة رقم ${successInvoice.invoice_number}.` : successInvoice.invoice_number}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{successInvoice.id ? (
|
||||||
|
<Link
|
||||||
|
href={`/sales_invoices/sales_invoices-view/?id=${successInvoice.id}`}
|
||||||
|
className="font-bold text-emerald-700 underline decoration-emerald-300 underline-offset-4"
|
||||||
|
>
|
||||||
|
فتح تفاصيل الفاتورة
|
||||||
|
</Link>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<CardBox>
|
||||||
|
<LoadingSpinner />
|
||||||
|
</CardBox>
|
||||||
|
) : !workspace.shops.length ? (
|
||||||
|
<CardBox>
|
||||||
|
<div className="space-y-4 py-8 text-center">
|
||||||
|
<h2 className="text-2xl font-bold text-slate-900">لا يوجد محل مرتبط بحسابك بعد</h2>
|
||||||
|
<p className="text-slate-600">أنشئ أول محل ليتم تفعيل شاشة الكاشير وتقارير اليوم.</p>
|
||||||
|
<div className="flex flex-wrap items-center justify-center gap-3">
|
||||||
|
{canCreateShops && <BaseButton href="/shops/shops-new" color="success" label="إضافة محل" />}
|
||||||
|
<BaseButton href="/shops/shops-list" color="info" label="عرض المحلات" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
) : (
|
||||||
|
<div className="grid gap-6 xl:grid-cols-[1.35fr,0.65fr]">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<CardBox className="border-0 bg-white shadow-lg shadow-sky-100/60">
|
||||||
|
<div className="grid gap-4 lg:grid-cols-[0.7fr,1.3fr] lg:items-end">
|
||||||
|
<div>
|
||||||
|
<label className="mb-2 block text-sm font-bold text-slate-700">المحل الحالي</label>
|
||||||
|
<select
|
||||||
|
value={selectedShopId}
|
||||||
|
onChange={(event) => loadWorkspace(event.target.value)}
|
||||||
|
className={`h-12 w-full border border-slate-200 bg-white px-4 text-right text-slate-800 transition ${focusRing} ${corners}`}
|
||||||
|
>
|
||||||
|
{(workspace.shops || []).map((shop) => (
|
||||||
|
<option key={shop.id} value={shop.id}>
|
||||||
|
{shop.shop_name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-3 md:grid-cols-3">
|
||||||
|
<div className="rounded-2xl border border-slate-100 bg-slate-50 px-4 py-3">
|
||||||
|
<div className="text-xs font-bold text-slate-500">العملة</div>
|
||||||
|
<div className="mt-1 text-lg font-bold text-slate-900">{workspace.selectedShop?.currency_name || 'دينار عراقي'}</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-2xl border border-slate-100 bg-slate-50 px-4 py-3">
|
||||||
|
<div className="text-xs font-bold text-slate-500">سعر الدولار الحالي</div>
|
||||||
|
<div className="mt-1 text-lg font-bold text-slate-900">{workspace.selectedShop?.usd_rate || 0}</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-2xl border border-slate-100 bg-slate-50 px-4 py-3">
|
||||||
|
<div className="text-xs font-bold text-slate-500">آخر تحديث</div>
|
||||||
|
<div className="mt-1 text-sm font-bold text-slate-900">{formatDateTime(workspace.latestPriceChange?.changed_at)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
|
||||||
|
<CardBox className="border-0 bg-white shadow-lg shadow-sky-100/60">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-bold text-slate-900">بحث سريع عن المنتجات</h2>
|
||||||
|
<p className="text-sm text-slate-500">اكتب اسم المنتج أو الباركود أو رمز المنتج، وستظهر النتائج فوراً بدون إعادة تحميل.</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<BaseButton href="/products/products-list" color="info" label="إدارة المنتجات" />
|
||||||
|
<BaseButton href="/categories/categories-list" color="info" label="إدارة الأقسام" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 lg:grid-cols-[1.3fr,0.7fr]">
|
||||||
|
<input
|
||||||
|
value={query}
|
||||||
|
onChange={(event) => setQuery(event.target.value)}
|
||||||
|
placeholder="ابحث باسم المنتج أو الباركود..."
|
||||||
|
className={`h-14 w-full border border-slate-200 bg-slate-50 px-4 text-right text-lg text-slate-900 transition ${focusRing} ${corners}`}
|
||||||
|
/>
|
||||||
|
<div className="rounded-2xl border border-sky-100 bg-sky-50 px-4 py-3 text-sm text-sky-800">
|
||||||
|
<div className="font-bold">اقتراحات مباشرة</div>
|
||||||
|
<div className="mt-2 flex flex-wrap gap-2">
|
||||||
|
{suggestions.length ? (
|
||||||
|
suggestions.map((product) => (
|
||||||
|
<button
|
||||||
|
key={product.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => addProductToCart(product.id)}
|
||||||
|
className="rounded-full bg-white px-3 py-1.5 font-bold text-slate-700 transition hover:-translate-y-0.5 hover:text-emerald-700"
|
||||||
|
>
|
||||||
|
{product.product_name}
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<span>ابدأ الكتابة لعرض الاقتراحات.</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setActiveCategoryId('all')}
|
||||||
|
className={`rounded-full px-4 py-2 text-sm font-bold transition ${
|
||||||
|
activeCategoryId === 'all'
|
||||||
|
? 'bg-emerald-600 text-white shadow-lg shadow-emerald-100'
|
||||||
|
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
كل الأقسام
|
||||||
|
</button>
|
||||||
|
{(workspace.categories || []).map((category) => (
|
||||||
|
<button
|
||||||
|
key={category.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setActiveCategoryId(category.id)}
|
||||||
|
className={`rounded-full px-4 py-2 text-sm font-bold transition ${
|
||||||
|
activeCategoryId === category.id
|
||||||
|
? 'bg-sky-600 text-white shadow-lg shadow-sky-100'
|
||||||
|
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{category.category_name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
|
||||||
|
{(workspace.products || []).length ? (
|
||||||
|
<div className="grid gap-4 sm:grid-cols-2 2xl:grid-cols-3">
|
||||||
|
{filteredProducts.map((product) => {
|
||||||
|
const dollarPrice = product.usd_price ?? ((product.sale_price || 0) / (workspace.selectedShop?.usd_rate || 1));
|
||||||
|
const lowStock = product.stock_quantity != null && product.stock_quantity <= 3;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={product.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => addProductToCart(product.id)}
|
||||||
|
className="group rounded-3xl border border-slate-100 bg-white p-5 text-right shadow-md shadow-slate-100/70 transition duration-200 hover:-translate-y-1 hover:border-emerald-200 hover:shadow-xl"
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg font-extrabold text-slate-900">{product.product_name}</div>
|
||||||
|
<div className="mt-1 text-sm text-slate-500">{product.category_name || 'بدون قسم'}</div>
|
||||||
|
</div>
|
||||||
|
<span className={`rounded-full px-3 py-1 text-xs font-bold ${lowStock ? 'bg-amber-100 text-amber-700' : 'bg-emerald-50 text-emerald-700'}`}>
|
||||||
|
{product.stock_quantity == null ? 'مخزون مفتوح' : `المخزون ${product.stock_quantity}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 grid gap-3 sm:grid-cols-2">
|
||||||
|
<div className="rounded-2xl bg-slate-50 px-3 py-3">
|
||||||
|
<div className="text-xs font-bold text-slate-500">سعر البيع</div>
|
||||||
|
<div className="mt-1 text-xl font-extrabold text-slate-900">{formatMoney(product.sale_price || 0)}</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-2xl bg-sky-50 px-3 py-3">
|
||||||
|
<div className="text-xs font-bold text-sky-600">السعر بالدولار</div>
|
||||||
|
<div className="mt-1 text-xl font-extrabold text-sky-900">{formatUsd(dollarPrice)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex items-center justify-between text-sm text-slate-500">
|
||||||
|
<span>{product.sku || product.barcode || 'منتج سريع البيع'}</span>
|
||||||
|
<span className="font-bold text-emerald-700 transition group-hover:text-emerald-800">أضف للفاتورة</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
emptyProductState
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<CardBox className="border-0 bg-white shadow-lg shadow-emerald-100/60">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-bold text-slate-900">الفاتورة الحالية</h2>
|
||||||
|
<p className="text-sm text-slate-500">أزرار كبيرة وواضحة مناسبة للاستخدام داخل المحل.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
{cartDetails.length ? (
|
||||||
|
cartDetails.map((item) => (
|
||||||
|
<div key={item.productId} className="rounded-2xl border border-slate-100 bg-slate-50 p-4">
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<div className="font-bold text-slate-900">{item.product.product_name}</div>
|
||||||
|
<div className="mt-1 text-sm text-slate-500">{item.product.category_name}</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => updateCartQuantity(item.productId, 0)}
|
||||||
|
className="text-sm font-bold text-red-500 transition hover:text-red-700"
|
||||||
|
>
|
||||||
|
حذف
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex items-center justify-between gap-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => updateCartQuantity(item.productId, item.quantity - 1)}
|
||||||
|
className="h-10 w-10 rounded-2xl bg-white text-xl font-bold text-slate-700 shadow-sm transition hover:bg-slate-100"
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</button>
|
||||||
|
<div className="min-w-14 rounded-2xl bg-white px-3 py-2 text-center text-lg font-extrabold text-slate-900 shadow-sm">
|
||||||
|
{item.quantity}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => updateCartQuantity(item.productId, item.quantity + 1)}
|
||||||
|
className="h-10 w-10 rounded-2xl bg-emerald-600 text-xl font-bold text-white shadow-lg shadow-emerald-100 transition hover:bg-emerald-700"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="text-left">
|
||||||
|
<div className="text-sm text-slate-500">إجمالي السطر</div>
|
||||||
|
<div className="text-lg font-extrabold text-slate-900">{formatMoney(item.lineTotal)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-8 text-center text-slate-500">
|
||||||
|
اختر منتجات من القائمة لتكوين الفاتورة.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-3xl bg-slate-900 p-5 text-white shadow-xl shadow-slate-200/80">
|
||||||
|
<div className="grid gap-3 text-sm">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-slate-300">عدد القطع</span>
|
||||||
|
<span className="text-xl font-extrabold">{cartSummary.quantity}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-slate-300">الإجمالي</span>
|
||||||
|
<span className="text-2xl font-extrabold text-emerald-300">{formatMoney(cartSummary.total)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-slate-300">الربح المتوقع</span>
|
||||||
|
<span className="text-lg font-bold text-sky-300">{formatMoney(cartSummary.profit)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="mb-2 block text-sm font-bold text-slate-700">طريقة الدفع</label>
|
||||||
|
<select
|
||||||
|
value={paymentMethod}
|
||||||
|
onChange={(event) => setPaymentMethod(event.target.value)}
|
||||||
|
className={`h-12 w-full border border-slate-200 bg-white px-4 text-right text-slate-800 transition ${focusRing} ${corners}`}
|
||||||
|
>
|
||||||
|
<option value="cash">نقدي</option>
|
||||||
|
<option value="card">بطاقة</option>
|
||||||
|
<option value="transfer">تحويل</option>
|
||||||
|
<option value="mixed">مختلط</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="mb-2 block text-sm font-bold text-slate-700">ملاحظات الفاتورة</label>
|
||||||
|
<textarea
|
||||||
|
value={notes}
|
||||||
|
onChange={(event) => setNotes(event.target.value)}
|
||||||
|
placeholder="مثال: زبون دائم - طلب سريع"
|
||||||
|
className={`min-h-28 w-full border border-slate-200 bg-white px-4 py-3 text-right text-slate-800 transition ${focusRing} ${corners}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BaseButton
|
||||||
|
color="success"
|
||||||
|
label={submitting ? 'جارٍ حفظ الفاتورة...' : canCheckout ? 'تأكيد البيع وحفظ الفاتورة' : 'لا تملك صلاحية إنشاء الفواتير'}
|
||||||
|
onClick={handleCheckout}
|
||||||
|
disabled={submitting || !cartDetails.length || !canCheckout}
|
||||||
|
className="!flex h-14 w-full !items-center !justify-center text-lg font-bold"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
|
||||||
|
<CardBox className="border-0 bg-white shadow-lg shadow-sky-100/60">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-bold text-slate-900">أدوات سعر الدولار</h2>
|
||||||
|
<p className="text-sm text-slate-500">تحديث سعر اليوم ثم تطبيق الزيادة أو الرجوع للسعر السابق.</p>
|
||||||
|
</div>
|
||||||
|
<span className="rounded-full bg-sky-50 px-3 py-1 text-xs font-bold text-sky-700">
|
||||||
|
آخر حركة: {workspace.latestPriceChange?.summary || 'لا توجد حركات بعد'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="mb-2 block text-sm font-bold text-slate-700">سعر الدولار اليومي</label>
|
||||||
|
<input
|
||||||
|
value={usdRateInput}
|
||||||
|
onChange={(event) => setUsdRateInput(event.target.value)}
|
||||||
|
placeholder="مثال: 1470"
|
||||||
|
className={`h-12 w-full border border-slate-200 bg-white px-4 text-right text-slate-800 transition ${focusRing} ${corners}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<BaseButton
|
||||||
|
color="info"
|
||||||
|
label={pricingBusy ? 'جارٍ الحفظ...' : 'حفظ سعر الدولار'}
|
||||||
|
onClick={() => handlePricingAction('set_rate')}
|
||||||
|
disabled={pricingBusy || !canManagePricing}
|
||||||
|
className="!flex h-12 w-full !items-center !justify-center font-bold"
|
||||||
|
/>
|
||||||
|
<BaseButton
|
||||||
|
color="success"
|
||||||
|
label={pricingBusy ? 'جارٍ تحديث الأسعار...' : 'تطبيق الأسعار حسب الدولار'}
|
||||||
|
onClick={() => handlePricingAction('apply_prices')}
|
||||||
|
disabled={pricingBusy || !canManagePricing}
|
||||||
|
className="!flex h-12 w-full !items-center !justify-center font-bold"
|
||||||
|
/>
|
||||||
|
<BaseButton
|
||||||
|
color="warning"
|
||||||
|
label={pricingBusy ? 'جارٍ الاسترجاع...' : 'إرجاع الأسعار السابقة'}
|
||||||
|
onClick={() => handlePricingAction('restore_prices')}
|
||||||
|
disabled={pricingBusy || !canManagePricing}
|
||||||
|
className="!flex h-12 w-full !items-center !justify-center font-bold"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{!canManagePricing ? <p className="text-xs text-slate-500">هذه الأدوات متاحة لمدير المحل أو من يملك صلاحية تحديث المحلات.</p> : null}
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
|
||||||
|
<CardBox className="border-0 bg-white shadow-lg shadow-slate-100/80">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-bold text-slate-900">فواتير اليوم</h2>
|
||||||
|
<p className="text-sm text-slate-500">قائمة مباشرة بآخر الفواتير المدفوعة مع الربح المحسوب.</p>
|
||||||
|
</div>
|
||||||
|
<BaseButton href="/sales_invoices/sales_invoices-list" color="info" label="كل الفواتير" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
{(workspace.recentInvoices || []).length ? (
|
||||||
|
workspace.recentInvoices.map((invoice) => (
|
||||||
|
<Link
|
||||||
|
key={invoice.id}
|
||||||
|
href={`/sales_invoices/sales_invoices-view/?id=${invoice.id}`}
|
||||||
|
className="block rounded-2xl border border-slate-100 bg-slate-50 px-4 py-4 transition hover:-translate-y-0.5 hover:border-emerald-200 hover:bg-white"
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<div className="font-extrabold text-slate-900">{invoice.invoice_number}</div>
|
||||||
|
<div className="mt-1 text-sm text-slate-500">{formatDateTime(invoice.sold_at)}</div>
|
||||||
|
</div>
|
||||||
|
<span className="rounded-full bg-white px-3 py-1 text-xs font-bold text-slate-600 shadow-sm">
|
||||||
|
{formatSalesInvoicePaymentMethod(invoice.payment_method)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 grid gap-2 sm:grid-cols-3">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-slate-500">إجمالي الفاتورة</div>
|
||||||
|
<div className="font-bold text-slate-900">{formatMoney(invoice.total_amount)}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-slate-500">الربح</div>
|
||||||
|
<div className="font-bold text-emerald-700">{formatMoney(invoice.total_profit_amount)}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-slate-500">عدد القطع</div>
|
||||||
|
<div className="font-bold text-slate-900">{invoice.item_count}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-8 text-center text-slate-500">
|
||||||
|
لم تُسجَّل أي فاتورة مدفوعة اليوم بعد.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</SectionMain>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
CashierPage.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return <LayoutAuthenticated permission="READ_PRODUCTS">{page}</LayoutAuthenticated>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CashierPage;
|
||||||
@ -249,10 +249,10 @@ const EditCategories = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Edit categories')}</title>
|
<title>{getPageTitle('تعديل القسم')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'Edit categories'} main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'تعديل القسم'} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -330,11 +330,11 @@ const EditCategories = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="اسمالقسم"
|
label="اسم القسم"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="category_name"
|
name="category_name"
|
||||||
placeholder="اسمالقسم"
|
placeholder="اسم القسم"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -405,12 +405,12 @@ const EditCategories = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="ترتيبالعرض"
|
label="ترتيب العرض"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="sort_order"
|
name="sort_order"
|
||||||
placeholder="ترتيبالعرض"
|
placeholder="ترتيب العرض"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -491,7 +491,7 @@ const EditCategories = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField label='organizations' labelFor='organizations'>
|
<FormField label='المنظمة' labelFor='organizations'>
|
||||||
<Field
|
<Field
|
||||||
name='organizations'
|
name='organizations'
|
||||||
id='organizations'
|
id='organizations'
|
||||||
@ -538,9 +538,9 @@ const EditCategories = () => {
|
|||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Submit" />
|
<BaseButton type="submit" color="info" label="حفظ" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="إعادة تعيين" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/categories/categories-list')}/>
|
<BaseButton type='reset' color='danger' outline label='إلغاء' onClick={() => router.push('/categories/categories-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@ -246,10 +246,10 @@ const EditCategoriesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Edit categories')}</title>
|
<title>{getPageTitle('تعديل القسم')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'Edit categories'} main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'تعديل القسم'} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -327,11 +327,11 @@ const EditCategoriesPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="اسمالقسم"
|
label="اسم القسم"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="category_name"
|
name="category_name"
|
||||||
placeholder="اسمالقسم"
|
placeholder="اسم القسم"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -402,12 +402,12 @@ const EditCategoriesPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="ترتيبالعرض"
|
label="ترتيب العرض"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="sort_order"
|
name="sort_order"
|
||||||
placeholder="ترتيبالعرض"
|
placeholder="ترتيب العرض"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -488,7 +488,7 @@ const EditCategoriesPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField label='organizations' labelFor='organizations'>
|
<FormField label='المنظمة' labelFor='organizations'>
|
||||||
<Field
|
<Field
|
||||||
name='organizations'
|
name='organizations'
|
||||||
id='organizations'
|
id='organizations'
|
||||||
@ -535,9 +535,9 @@ const EditCategoriesPage = () => {
|
|||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Submit" />
|
<BaseButton type="submit" color="info" label="حفظ" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="إعادة تعيين" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/categories/categories-list')}/>
|
<BaseButton type='reset' color='danger' outline label='إلغاء' onClick={() => router.push('/categories/categories-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@ -34,8 +34,8 @@ const CategoriesTablesPage = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
|
||||||
const [filters] = useState([{label: 'اسمالقسم', title: 'category_name'},{label: 'الوصف', title: 'description'},
|
const [filters] = useState([{label: 'اسم القسم', title: 'category_name'},{label: 'الوصف', title: 'description'},
|
||||||
{label: 'ترتيبالعرض', title: 'sort_order', number: 'true'},
|
{label: 'ترتيب العرض', title: 'sort_order', number: 'true'},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -90,28 +90,28 @@ const CategoriesTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Categories')}</title>
|
<title>{getPageTitle('الأقسام')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Categories" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="الأقسام" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/categories/categories-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/categories/categories-new'} color='info' label='إضافة قسم'/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label='إضافة تصفية'
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getCategoriesCSV} />
|
<BaseButton className={'mr-3'} color='info' label='تنزيل CSV' onClick={getCategoriesCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label='رفع CSV'
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -133,9 +133,9 @@ const CategoriesTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title='رفع CSV'
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={'تأكيد'}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -142,10 +142,10 @@ const CategoriesNew = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('New Item')}</title>
|
<title>{getPageTitle('إضافة قسم')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Item" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="إضافة قسم" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -192,11 +192,11 @@ const CategoriesNew = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="اسمالقسم"
|
label="اسم القسم"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="category_name"
|
name="category_name"
|
||||||
placeholder="اسمالقسم"
|
placeholder="اسم القسم"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -263,12 +263,12 @@ const CategoriesNew = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="ترتيبالعرض"
|
label="ترتيب العرض"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="sort_order"
|
name="sort_order"
|
||||||
placeholder="ترتيبالعرض"
|
placeholder="ترتيب العرض"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -344,7 +344,7 @@ const CategoriesNew = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField label="organizations" labelFor="organizations">
|
<FormField label="المنظمة" labelFor="organizations">
|
||||||
<Field name="organizations" id="organizations" component={SelectField} options={[]} itemRef={'organizations'}></Field>
|
<Field name="organizations" id="organizations" component={SelectField} options={[]} itemRef={'organizations'}></Field>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -356,9 +356,9 @@ const CategoriesNew = () => {
|
|||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Submit" />
|
<BaseButton type="submit" color="info" label="حفظ" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="إعادة تعيين" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/categories/categories-list')}/>
|
<BaseButton type='reset' color='danger' outline label='إلغاء' onClick={() => router.push('/categories/categories-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@ -34,8 +34,8 @@ const CategoriesTablesPage = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
|
||||||
const [filters] = useState([{label: 'اسمالقسم', title: 'category_name'},{label: 'الوصف', title: 'description'},
|
const [filters] = useState([{label: 'اسم القسم', title: 'category_name'},{label: 'الوصف', title: 'description'},
|
||||||
{label: 'ترتيبالعرض', title: 'sort_order', number: 'true'},
|
{label: 'ترتيب العرض', title: 'sort_order', number: 'true'},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -90,28 +90,28 @@ const CategoriesTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Categories')}</title>
|
<title>{getPageTitle('الأقسام')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Categories" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="الأقسام" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/categories/categories-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/categories/categories-new'} color='info' label='إضافة قسم'/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label='إضافة تصفية'
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getCategoriesCSV} />
|
<BaseButton className={'mr-3'} color='info' label='تنزيل CSV' onClick={getCategoriesCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label='رفع CSV'
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -120,7 +120,7 @@ const CategoriesTablesPage = () => {
|
|||||||
<div id='delete-rows-button'></div>
|
<div id='delete-rows-button'></div>
|
||||||
|
|
||||||
<Link href={'/categories/categories-list'}>
|
<Link href={'/categories/categories-list'}>
|
||||||
Back to <span className='capitalize'>table</span>
|
العودة إلى <span className='capitalize'>الجدول</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -135,9 +135,9 @@ const CategoriesTablesPage = () => {
|
|||||||
</CardBox>
|
</CardBox>
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title='رفع CSV'
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={'تأكيد'}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -33,10 +33,6 @@ const CategoriesView = () => {
|
|||||||
|
|
||||||
const { id } = router.query;
|
const { id } = router.query;
|
||||||
|
|
||||||
function removeLastCharacter(str) {
|
|
||||||
console.log(str,`str`)
|
|
||||||
return str.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetch({ id }));
|
dispatch(fetch({ id }));
|
||||||
@ -46,13 +42,13 @@ const CategoriesView = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('View categories')}</title>
|
<title>{getPageTitle('تفاصيل القسم')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={removeLastCharacter('View categories')} main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'تفاصيل القسم'} main>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Edit'
|
label='تعديل'
|
||||||
href={`/categories/categories-edit/?id=${id}`}
|
href={`/categories/categories-edit/?id=${id}`}
|
||||||
/>
|
/>
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
@ -91,7 +87,7 @@ const CategoriesView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<p>{categories?.shop?.shop_name ?? 'No data'}</p>
|
<p>{categories?.shop?.shop_name ?? 'لا توجد بيانات'}</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -117,7 +113,7 @@ const CategoriesView = () => {
|
|||||||
|
|
||||||
|
|
||||||
<div className={'mb-4'}>
|
<div className={'mb-4'}>
|
||||||
<p className={'block font-bold mb-2'}>اسمالقسم</p>
|
<p className={'block font-bold mb-2'}>اسم القسم</p>
|
||||||
<p>{categories?.category_name}</p>
|
<p>{categories?.category_name}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -150,7 +146,7 @@ const CategoriesView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField label='Multi Text' hasTextareaHeight>
|
<FormField label='الوصف' hasTextareaHeight>
|
||||||
<textarea className={'w-full'} disabled value={categories?.description} />
|
<textarea className={'w-full'} disabled value={categories?.description} />
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -186,8 +182,8 @@ const CategoriesView = () => {
|
|||||||
|
|
||||||
|
|
||||||
<div className={'mb-4'}>
|
<div className={'mb-4'}>
|
||||||
<p className={'block font-bold mb-2'}>ترتيبالعرض</p>
|
<p className={'block font-bold mb-2'}>ترتيب العرض</p>
|
||||||
<p>{categories?.sort_order || 'No data'}</p>
|
<p>{categories?.sort_order || 'لا توجد بيانات'}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -266,7 +262,7 @@ const CategoriesView = () => {
|
|||||||
|
|
||||||
|
|
||||||
<div className={'mb-4'}>
|
<div className={'mb-4'}>
|
||||||
<p className={'block font-bold mb-2'}>organizations</p>
|
<p className={'block font-bold mb-2'}>المنظمة</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -275,7 +271,7 @@ const CategoriesView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<p>{categories?.organizations?.name ?? 'No data'}</p>
|
<p>{categories?.organizations?.name ?? 'لا توجد بيانات'}</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -309,7 +305,7 @@ const CategoriesView = () => {
|
|||||||
|
|
||||||
|
|
||||||
<>
|
<>
|
||||||
<p className={'block font-bold mb-2'}>Products القسم</p>
|
<p className={'block font-bold mb-2'}>منتجات القسم</p>
|
||||||
<CardBox
|
<CardBox
|
||||||
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||||
hasTable
|
hasTable
|
||||||
@ -324,11 +320,11 @@ const CategoriesView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>اسمالمنتج</th>
|
<th>اسم المنتج</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>رمزالمنتج</th>
|
<th>رمز المنتج</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -336,37 +332,37 @@ const CategoriesView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>سعرالتكلفةالحقيقي</th>
|
<th>سعر التكلفة</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>سعرالبيع</th>
|
<th>سعر البيع</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>سعرالبيعالسابق</th>
|
<th>سعر البيع السابق</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>سعرالتكلفةالسابق</th>
|
<th>سعر التكلفة السابق</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>السعربالدولار</th>
|
<th>السعر بالدولار</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>الكميةبالمخزون</th>
|
<th>الكمية بالمخزون</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>حدالتنبيهلنقصالمخزون</th>
|
<th>حد التنبيه لنقص المخزون</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>متاحللبيع</th>
|
<th>متاح للبيع</th>
|
||||||
|
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
@ -381,61 +377,61 @@ const CategoriesView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<td data-label="product_name">
|
<td data-label="اسم المنتج">
|
||||||
{ item.product_name }
|
{ item.product_name }
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<td data-label="sku">
|
<td data-label="رمز المنتج">
|
||||||
{ item.sku }
|
{ item.sku }
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<td data-label="barcode">
|
<td data-label="الباركود">
|
||||||
{ item.barcode }
|
{ item.barcode }
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<td data-label="cost_price">
|
<td data-label="سعر التكلفة">
|
||||||
{ item.cost_price }
|
{ item.cost_price }
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<td data-label="sale_price">
|
<td data-label="سعر البيع">
|
||||||
{ item.sale_price }
|
{ item.sale_price }
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<td data-label="sale_price_backup">
|
<td data-label="سعر البيع السابق">
|
||||||
{ item.sale_price_backup }
|
{ item.sale_price_backup }
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<td data-label="cost_price_backup">
|
<td data-label="سعر التكلفة السابق">
|
||||||
{ item.cost_price_backup }
|
{ item.cost_price_backup }
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<td data-label="usd_price">
|
<td data-label="السعر بالدولار">
|
||||||
{ item.usd_price }
|
{ item.usd_price }
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<td data-label="stock_quantity">
|
<td data-label="الكمية بالمخزون">
|
||||||
{ item.stock_quantity }
|
{ item.stock_quantity }
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<td data-label="low_stock_threshold">
|
<td data-label="حد التنبيه لنقص المخزون">
|
||||||
{ item.low_stock_threshold }
|
{ item.low_stock_threshold }
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
@ -443,7 +439,7 @@ const CategoriesView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<td data-label="is_active">
|
<td data-label="الحالة">
|
||||||
{ dataFormatter.booleanFormatter(item.is_active) }
|
{ dataFormatter.booleanFormatter(item.is_active) }
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
@ -453,7 +449,7 @@ const CategoriesView = () => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{!categories?.products_category?.length && <div className={'text-center py-4'}>No data</div>}
|
{!categories?.products_category?.length && <div className={'text-center py-4'}>لا توجد بيانات</div>}
|
||||||
</CardBox>
|
</CardBox>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
@ -466,7 +462,7 @@ const CategoriesView = () => {
|
|||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Back'
|
label='رجوع'
|
||||||
onClick={() => router.push('/categories/categories-list')}
|
onClick={() => router.push('/categories/categories-list')}
|
||||||
/>
|
/>
|
||||||
</CardBox>
|
</CardBox>
|
||||||
|
|||||||
@ -1,441 +1,388 @@
|
|||||||
import * as icon from '@mdi/js';
|
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||||
import Head from 'next/head'
|
import Head from 'next/head';
|
||||||
import React from 'react'
|
import Link from 'next/link';
|
||||||
|
import React, { ReactElement, useEffect, useMemo, useState } from 'react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import type { ReactElement } from 'react'
|
|
||||||
import LayoutAuthenticated from '../layouts/Authenticated'
|
|
||||||
import SectionMain from '../components/SectionMain'
|
|
||||||
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'
|
|
||||||
import BaseIcon from "../components/BaseIcon";
|
|
||||||
import { getPageTitle } from '../config'
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
import { hasPermission } from "../helpers/userPermissions";
|
import BaseButton from '../components/BaseButton';
|
||||||
import { fetchWidgets } from '../stores/roles/rolesSlice';
|
import CardBox from '../components/CardBox';
|
||||||
import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
|
import LoadingSpinner from '../components/LoadingSpinner';
|
||||||
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
|
import SectionMain from '../components/SectionMain';
|
||||||
|
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
|
||||||
|
import { getPageTitle } from '../config';
|
||||||
|
import { formatSalesInvoicePaymentMethod } from '../helpers/salesInvoiceLabels';
|
||||||
|
import { hasPermission } from '../helpers/userPermissions';
|
||||||
|
import LayoutAuthenticated from '../layouts/Authenticated';
|
||||||
|
import { useAppSelector } from '../stores/hooks';
|
||||||
|
|
||||||
|
type PosSummary = {
|
||||||
|
totalSales: number;
|
||||||
|
totalProfit: number;
|
||||||
|
invoiceCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PosInvoice = {
|
||||||
|
id: string;
|
||||||
|
invoice_number: string;
|
||||||
|
sold_at: string;
|
||||||
|
total_amount: number;
|
||||||
|
total_profit_amount: number;
|
||||||
|
payment_method: string;
|
||||||
|
item_count: number;
|
||||||
|
cashier_name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PosWorkspace = {
|
||||||
|
selectedShop: {
|
||||||
|
id: string;
|
||||||
|
shop_name: string;
|
||||||
|
currency_name: string;
|
||||||
|
usd_rate: number;
|
||||||
|
} | null;
|
||||||
|
summary: PosSummary;
|
||||||
|
recentInvoices: PosInvoice[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const emptyWorkspace: PosWorkspace = {
|
||||||
|
selectedShop: null,
|
||||||
|
summary: {
|
||||||
|
totalSales: 0,
|
||||||
|
totalProfit: 0,
|
||||||
|
invoiceCount: 0,
|
||||||
|
},
|
||||||
|
recentInvoices: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatMoney = (value: number) => `${new Intl.NumberFormat('ar-IQ').format(value || 0)} د.ع`;
|
||||||
|
|
||||||
|
const formatNumber = (value: number) => new Intl.NumberFormat('ar-IQ').format(value || 0);
|
||||||
|
|
||||||
|
const formatDateTime = (value?: string) => {
|
||||||
|
if (!value) {
|
||||||
|
return '--';
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(value).toLocaleString('ar-IQ', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const dispatch = useAppDispatch();
|
const { currentUser } = useAppSelector((state) => state.auth);
|
||||||
const iconsColor = useAppSelector((state) => state.style.iconsColor);
|
const corners = useAppSelector((state) => state.style.corners);
|
||||||
const corners = useAppSelector((state) => state.style.corners);
|
|
||||||
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
|
|
||||||
|
|
||||||
const loadingMessage = 'Loading...';
|
const [workspace, setWorkspace] = useState<PosWorkspace>(emptyWorkspace);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
|
|
||||||
|
const canReadProducts = hasPermission(currentUser, 'READ_PRODUCTS');
|
||||||
|
const canReadInvoices = hasPermission(currentUser, 'READ_SALES_INVOICES');
|
||||||
|
const canReadShops = hasPermission(currentUser, 'READ_SHOPS');
|
||||||
|
|
||||||
const [users, setUsers] = React.useState(loadingMessage);
|
useEffect(() => {
|
||||||
const [roles, setRoles] = React.useState(loadingMessage);
|
if (!currentUser) {
|
||||||
const [permissions, setPermissions] = React.useState(loadingMessage);
|
return;
|
||||||
const [organizations, setOrganizations] = React.useState(loadingMessage);
|
|
||||||
const [shops, setShops] = React.useState(loadingMessage);
|
|
||||||
const [categories, setCategories] = React.useState(loadingMessage);
|
|
||||||
const [products, setProducts] = React.useState(loadingMessage);
|
|
||||||
const [sales_invoices, setSales_invoices] = React.useState(loadingMessage);
|
|
||||||
const [sales_invoice_items, setSales_invoice_items] = React.useState(loadingMessage);
|
|
||||||
const [price_change_logs, setPrice_change_logs] = React.useState(loadingMessage);
|
|
||||||
|
|
||||||
|
|
||||||
const [widgetsRole, setWidgetsRole] = React.useState({
|
|
||||||
role: { value: '', label: '' },
|
|
||||||
});
|
|
||||||
const { currentUser } = useAppSelector((state) => state.auth);
|
|
||||||
const { isFetchingQuery } = useAppSelector((state) => state.openAi);
|
|
||||||
|
|
||||||
const { rolesWidgets, loading } = useAppSelector((state) => state.roles);
|
|
||||||
|
|
||||||
|
|
||||||
const organizationId = currentUser?.organizations?.id;
|
|
||||||
|
|
||||||
async function loadData() {
|
|
||||||
const entities = ['users','roles','permissions','organizations','shops','categories','products','sales_invoices','sales_invoice_items','price_change_logs',];
|
|
||||||
const fns = [setUsers,setRoles,setPermissions,setOrganizations,setShops,setCategories,setProducts,setSales_invoices,setSales_invoice_items,setPrice_change_logs,];
|
|
||||||
|
|
||||||
const requests = entities.map((entity, index) => {
|
|
||||||
|
|
||||||
if(hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) {
|
|
||||||
return axios.get(`/${entity.toLowerCase()}/count`);
|
|
||||||
} else {
|
|
||||||
fns[index](null);
|
|
||||||
return Promise.resolve({data: {count: null}});
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
Promise.allSettled(requests).then((results) => {
|
|
||||||
results.forEach((result, i) => {
|
|
||||||
if (result.status === 'fulfilled') {
|
|
||||||
fns[i](result.value.data.count);
|
|
||||||
} else {
|
|
||||||
fns[i](result.reason.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getWidgets(roleId) {
|
if (!canReadProducts) {
|
||||||
await dispatch(fetchWidgets(roleId));
|
setLoading(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
React.useEffect(() => {
|
|
||||||
if (!currentUser) return;
|
|
||||||
loadData().then();
|
|
||||||
setWidgetsRole({ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name } });
|
|
||||||
}, [currentUser]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
const loadWorkspace = async () => {
|
||||||
if (!currentUser || !widgetsRole?.role?.value) return;
|
setLoading(true);
|
||||||
getWidgets(widgetsRole?.role?.value || '').then();
|
setErrorMessage('');
|
||||||
}, [widgetsRole?.role?.value]);
|
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get('/pos/workspace');
|
||||||
|
setWorkspace({
|
||||||
|
selectedShop: data.selectedShop || null,
|
||||||
|
summary: data.summary || emptyWorkspace.summary,
|
||||||
|
recentInvoices: Array.isArray(data.recentInvoices) ? data.recentInvoices : [],
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Dashboard workspace load failed:', error);
|
||||||
|
setErrorMessage(error?.response?.data || 'تعذر تحميل ملخص اليوم الآن.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadWorkspace();
|
||||||
|
}, [canReadProducts, currentUser]);
|
||||||
|
|
||||||
|
const quickActions = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
href: '/cashier',
|
||||||
|
label: 'فتح الكاشير',
|
||||||
|
description: 'ابدأ عملية بيع جديدة بسرعة وابحث عن المنتج مباشرة.',
|
||||||
|
visible: canReadProducts,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/products/products-list',
|
||||||
|
label: 'إدارة المنتجات',
|
||||||
|
description: 'أضف المنتجات وعدّل الأسعار والكميات من مكان واحد.',
|
||||||
|
visible: canReadProducts,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/sales_invoices/sales_invoices-list',
|
||||||
|
label: 'مراجعة الفواتير',
|
||||||
|
description: 'تابع المبيعات اليومية وشاهد الأرباح وتفاصيل كل فاتورة.',
|
||||||
|
visible: canReadInvoices,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/shops/shops-list',
|
||||||
|
label: 'إعدادات المحل',
|
||||||
|
description: 'حدّث بيانات المحل والعملة وسعر الدولار اليومي.',
|
||||||
|
visible: canReadShops,
|
||||||
|
},
|
||||||
|
].filter((item) => item.visible),
|
||||||
|
[canReadInvoices, canReadProducts, canReadShops],
|
||||||
|
);
|
||||||
|
|
||||||
|
const startSteps = useMemo(
|
||||||
|
() => [
|
||||||
|
'أضف المنتجات مع سعر التكلفة وسعر البيع والكمية.',
|
||||||
|
'افتح شاشة الكاشير واختر المنتجات المطلوبة بسرعة.',
|
||||||
|
'احفظ الفاتورة ثم راقب ملخص اليوم من هذه الصفحة.',
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const stats = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
label: 'المحل الحالي',
|
||||||
|
value: workspace.selectedShop?.shop_name || 'غير محدد بعد',
|
||||||
|
hint: workspace.selectedShop?.currency_name || 'أضف محلًا واحدًا للبدء',
|
||||||
|
cardClassName: 'border border-emerald-100 bg-white shadow-sm shadow-emerald-100/70',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'مبيعات اليوم',
|
||||||
|
value: formatMoney(workspace.summary.totalSales),
|
||||||
|
hint: 'إجمالي قيمة الفواتير المدفوعة اليوم',
|
||||||
|
cardClassName: 'border border-sky-100 bg-white shadow-sm shadow-sky-100/70',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ربح اليوم',
|
||||||
|
value: formatMoney(workspace.summary.totalProfit),
|
||||||
|
hint: 'صافي الفرق بين سعر البيع وسعر التكلفة',
|
||||||
|
cardClassName: 'border border-violet-100 bg-white shadow-sm shadow-violet-100/70',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'عدد الفواتير اليوم',
|
||||||
|
value: formatNumber(workspace.summary.invoiceCount),
|
||||||
|
hint: 'عدد عمليات البيع المسجلة اليوم',
|
||||||
|
cardClassName: 'border border-amber-100 bg-white shadow-sm shadow-amber-100/70',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[workspace],
|
||||||
|
);
|
||||||
|
|
||||||
|
const welcomeName = currentUser?.firstName || 'بك';
|
||||||
|
const primaryAction = quickActions[0];
|
||||||
|
const usdRate = workspace.selectedShop?.usd_rate ? formatNumber(workspace.selectedShop.usd_rate) : '--';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>
|
<title>{getPageTitle('الواجهة الرئيسية')}</title>
|
||||||
{getPageTitle('Overview')}
|
|
||||||
</title>
|
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton
|
<SectionTitleLineWithButton
|
||||||
icon={icon.mdiChartTimelineVariant}
|
icon={mdiChartTimelineVariant}
|
||||||
title='Overview'
|
title="الواجهة الرئيسية"
|
||||||
main>
|
main
|
||||||
|
>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
|
|
||||||
{hasPermission(currentUser, 'CREATE_ROLES') && <WidgetCreator
|
<div className="mb-6 grid gap-6 xl:grid-cols-[1.2fr,0.8fr]">
|
||||||
currentUser={currentUser}
|
<CardBox className="border border-emerald-100 bg-gradient-to-l from-white via-emerald-50 to-sky-50 shadow-lg shadow-emerald-100/60">
|
||||||
isFetchingQuery={isFetchingQuery}
|
<div className="space-y-5">
|
||||||
setWidgetsRole={setWidgetsRole}
|
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||||
widgetsRole={widgetsRole}
|
<div>
|
||||||
/>}
|
<span className="inline-flex rounded-full border border-emerald-200 bg-white px-3 py-1 text-xs font-bold text-emerald-700">
|
||||||
{!!rolesWidgets.length &&
|
ملخص سريع وواضح لعمل اليوم
|
||||||
hasPermission(currentUser, 'CREATE_ROLES') && (
|
</span>
|
||||||
<p className=' text-gray-500 dark:text-gray-400 mb-4'>
|
<h2 className="mt-3 text-3xl font-extrabold text-slate-950">أهلاً {welcomeName}</h2>
|
||||||
{`${widgetsRole?.role?.label || 'Users'}'s widgets`}
|
<p className="mt-2 max-w-2xl text-sm leading-7 text-slate-600">
|
||||||
</p>
|
هذه الصفحة تجمع لك أهم ما تحتاجه: حالة المحل الحالية، أرقام اليوم، وروابط سريعة للمنتجات والكاشير والفواتير.
|
||||||
)}
|
</p>
|
||||||
|
|
||||||
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-6 grid-flow-dense'>
|
|
||||||
{(isFetchingQuery || loading) && (
|
|
||||||
<div className={` ${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 text-lg leading-tight text-gray-500 flex items-center ${cardsStyle} dark:border-dark-700 p-6`}>
|
|
||||||
<BaseIcon
|
|
||||||
className={`${iconsColor} animate-spin mr-5`}
|
|
||||||
w='w-16'
|
|
||||||
h='h-16'
|
|
||||||
size={48}
|
|
||||||
path={icon.mdiLoading}
|
|
||||||
/>{' '}
|
|
||||||
Loading widgets...
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{ rolesWidgets &&
|
{primaryAction ? (
|
||||||
rolesWidgets.map((widget) => (
|
<BaseButton
|
||||||
<SmartWidget
|
href={primaryAction.href}
|
||||||
key={widget.id}
|
color="success"
|
||||||
userId={currentUser?.id}
|
label={primaryAction.label}
|
||||||
widget={widget}
|
className="!px-5 !py-3 font-bold"
|
||||||
roleId={widgetsRole?.role?.value || ''}
|
/>
|
||||||
admin={hasPermission(currentUser, 'CREATE_ROLES')}
|
) : null}
|
||||||
/>
|
</div>
|
||||||
))}
|
|
||||||
|
<div className="grid gap-3 md:grid-cols-3">
|
||||||
|
<div className={`${corners !== 'rounded-full' ? corners : 'rounded-3xl'} border border-white bg-white/90 px-4 py-4`}>
|
||||||
|
<div className="text-xs font-bold text-slate-500">المحل</div>
|
||||||
|
<div className="mt-2 text-lg font-extrabold text-slate-900">{workspace.selectedShop?.shop_name || 'غير محدد بعد'}</div>
|
||||||
|
</div>
|
||||||
|
<div className={`${corners !== 'rounded-full' ? corners : 'rounded-3xl'} border border-white bg-white/90 px-4 py-4`}>
|
||||||
|
<div className="text-xs font-bold text-slate-500">سعر الدولار اليومي</div>
|
||||||
|
<div className="mt-2 text-lg font-extrabold text-slate-900">{usdRate}</div>
|
||||||
|
</div>
|
||||||
|
<div className={`${corners !== 'rounded-full' ? corners : 'rounded-3xl'} border border-white bg-white/90 px-4 py-4`}>
|
||||||
|
<div className="text-xs font-bold text-slate-500">الروابط السريعة</div>
|
||||||
|
<div className="mt-2 text-lg font-extrabold text-slate-900">{formatNumber(quickActions.length)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
|
||||||
|
<CardBox className="border border-slate-100 bg-white shadow-lg shadow-slate-100/70">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold text-slate-900">ابدأ من هنا</h3>
|
||||||
|
<p className="mt-1 text-sm leading-6 text-slate-500">
|
||||||
|
رتّبت الواجهة لتكون بسيطة: أضف البيانات مرة واحدة، ثم استخدم الكاشير، وبعدها راقب النتائج يوميًا.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
{startSteps.map((step, index) => (
|
||||||
|
<div
|
||||||
|
key={step}
|
||||||
|
className={`${corners !== 'rounded-full' ? corners : 'rounded-3xl'} flex items-start gap-3 border border-slate-100 bg-slate-50 px-4 py-3`}
|
||||||
|
>
|
||||||
|
<span className="mt-0.5 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-emerald-100 text-sm font-extrabold text-emerald-700">
|
||||||
|
{index + 1}
|
||||||
|
</span>
|
||||||
|
<p className="text-sm font-medium leading-6 text-slate-700">{step}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{primaryAction ? (
|
||||||
|
<BaseButton href={primaryAction.href} color="info" label={`الانتقال إلى ${primaryAction.label}`} />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!!rolesWidgets.length && <hr className='my-6 ' />}
|
{errorMessage ? (
|
||||||
|
<div className="mb-6 rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||||
|
{errorMessage}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div id="dashboard" className='grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6'>
|
<div className="mb-6 grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||||
|
{stats.map((item) => (
|
||||||
|
<CardBox key={item.label} className={item.cardClassName}>
|
||||||
|
<div className="text-sm font-bold text-slate-500">{item.label}</div>
|
||||||
|
<div className="mt-2 text-2xl font-extrabold text-slate-900">{item.value}</div>
|
||||||
|
<div className="mt-1 text-xs text-slate-500">{item.hint}</div>
|
||||||
|
</CardBox>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-6 xl:grid-cols-[0.92fr,1.08fr]">
|
||||||
{hasPermission(currentUser, 'READ_USERS') && <Link href={'/users/users-list'}>
|
<CardBox className="border border-slate-100 bg-white shadow-lg shadow-slate-100/70">
|
||||||
<div
|
<div className="space-y-4">
|
||||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
<div className="flex items-start justify-between gap-3">
|
||||||
>
|
<div>
|
||||||
<div className="flex justify-between align-center">
|
<h3 className="text-xl font-bold text-slate-900">الصفحات المهمة</h3>
|
||||||
<div>
|
<p className="mt-1 text-sm text-slate-500">
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
هذه أهم الروابط اليومية بعد تنظيف القائمة الجانبية وتبسيط الواجهة.
|
||||||
Users
|
</p>
|
||||||
</div>
|
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
|
||||||
{users}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<BaseIcon
|
|
||||||
className={`${iconsColor}`}
|
|
||||||
w="w-16"
|
|
||||||
h="h-16"
|
|
||||||
size={48}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
path={icon.mdiAccountGroup || icon.mdiTable}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Link>}
|
<span className="rounded-full bg-slate-100 px-3 py-1 text-xs font-bold text-slate-600">
|
||||||
|
{formatNumber(quickActions.length)} روابط
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{hasPermission(currentUser, 'READ_ROLES') && <Link href={'/roles/roles-list'}>
|
{!quickActions.length ? (
|
||||||
<div
|
<div className="rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-8 text-center text-sm text-slate-500">
|
||||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
لا توجد صفحات متاحة لك حاليًا حسب الصلاحيات.
|
||||||
>
|
|
||||||
<div className="flex justify-between align-center">
|
|
||||||
<div>
|
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
|
||||||
Roles
|
|
||||||
</div>
|
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
|
||||||
{roles}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<BaseIcon
|
|
||||||
className={`${iconsColor}`}
|
|
||||||
w="w-16"
|
|
||||||
h="h-16"
|
|
||||||
size={48}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
path={icon.mdiShieldAccountVariantOutline || icon.mdiTable}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Link>}
|
) : (
|
||||||
|
<div className="grid gap-3">
|
||||||
{hasPermission(currentUser, 'READ_PERMISSIONS') && <Link href={'/permissions/permissions-list'}>
|
{quickActions.map((item) => (
|
||||||
<div
|
<Link
|
||||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
key={item.href}
|
||||||
>
|
href={item.href}
|
||||||
<div className="flex justify-between align-center">
|
className={`${corners !== 'rounded-full' ? corners : 'rounded-3xl'} block border border-slate-100 bg-slate-50 px-4 py-4 transition hover:border-emerald-200 hover:bg-emerald-50/60`}
|
||||||
<div>
|
>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-base font-bold text-slate-900">{item.label}</div>
|
||||||
Permissions
|
<div className="mt-1 text-sm leading-6 text-slate-600">{item.description}</div>
|
||||||
</div>
|
</Link>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
))}
|
||||||
{permissions}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<BaseIcon
|
|
||||||
className={`${iconsColor}`}
|
|
||||||
w="w-16"
|
|
||||||
h="h-16"
|
|
||||||
size={48}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
path={icon.mdiShieldAccountOutline || icon.mdiTable}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Link>}
|
)}
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
|
||||||
{hasPermission(currentUser, 'READ_ORGANIZATIONS') && <Link href={'/organizations/organizations-list'}>
|
<CardBox className="border border-slate-100 bg-white shadow-lg shadow-slate-100/70">
|
||||||
<div
|
<div className="space-y-4">
|
||||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
<div className="flex items-center justify-between gap-3">
|
||||||
>
|
<div>
|
||||||
<div className="flex justify-between align-center">
|
<h3 className="text-xl font-bold text-slate-900">آخر فواتير اليوم</h3>
|
||||||
<div>
|
<p className="mt-1 text-sm text-slate-500">لمتابعة حركة البيع بسرعة من نفس الواجهة.</p>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
|
||||||
Organizations
|
|
||||||
</div>
|
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
|
||||||
{organizations}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<BaseIcon
|
|
||||||
className={`${iconsColor}`}
|
|
||||||
w="w-16"
|
|
||||||
h="h-16"
|
|
||||||
size={48}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
path={icon.mdiTable || icon.mdiTable}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Link>}
|
{canReadInvoices && (
|
||||||
|
<BaseButton href="/sales_invoices/sales_invoices-list" color="info" label="كل الفواتير" small />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{hasPermission(currentUser, 'READ_SHOPS') && <Link href={'/shops/shops-list'}>
|
{loading ? (
|
||||||
<div
|
<LoadingSpinner />
|
||||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
) : !workspace.recentInvoices.length ? (
|
||||||
>
|
<div className="rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-8 text-center text-sm text-slate-500">
|
||||||
<div className="flex justify-between align-center">
|
لا توجد فواتير مدفوعة اليوم حتى الآن.
|
||||||
<div>
|
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
|
||||||
Shops
|
|
||||||
</div>
|
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
|
||||||
{shops}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<BaseIcon
|
|
||||||
className={`${iconsColor}`}
|
|
||||||
w="w-16"
|
|
||||||
h="h-16"
|
|
||||||
size={48}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
path={'mdiStorefront' in icon ? icon['mdiStorefront' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Link>}
|
) : (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{workspace.recentInvoices.slice(0, 6).map((invoice) => (
|
||||||
|
<div
|
||||||
|
key={invoice.id}
|
||||||
|
className={`${corners !== 'rounded-full' ? corners : 'rounded-3xl'} border border-slate-100 bg-slate-50 px-4 py-4`}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-bold text-slate-900">{invoice.invoice_number}</div>
|
||||||
|
<div className="mt-1 text-xs text-slate-500">
|
||||||
|
{formatDateTime(invoice.sold_at)}
|
||||||
|
{invoice.cashier_name ? ` • ${invoice.cashier_name}` : ''}
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 inline-flex rounded-full bg-white px-3 py-1 text-xs font-bold text-slate-600">
|
||||||
|
{formatSalesInvoicePaymentMethod(invoice.payment_method)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{hasPermission(currentUser, 'READ_CATEGORIES') && <Link href={'/categories/categories-list'}>
|
<div className="grid gap-1 text-sm lg:text-left">
|
||||||
<div
|
<div className="font-bold text-slate-900">الإجمالي: {formatMoney(invoice.total_amount)}</div>
|
||||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
<div className="text-slate-600">الربح: {formatMoney(invoice.total_profit_amount)}</div>
|
||||||
>
|
<div className="text-slate-500">عدد القطع: {formatNumber(invoice.item_count || 0)}</div>
|
||||||
<div className="flex justify-between align-center">
|
|
||||||
<div>
|
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
|
||||||
Categories
|
|
||||||
</div>
|
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
|
||||||
{categories}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<BaseIcon
|
|
||||||
className={`${iconsColor}`}
|
|
||||||
w="w-16"
|
|
||||||
h="h-16"
|
|
||||||
size={48}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
path={'mdiShape' in icon ? icon['mdiShape' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Link>}
|
)}
|
||||||
|
</div>
|
||||||
{hasPermission(currentUser, 'READ_PRODUCTS') && <Link href={'/products/products-list'}>
|
</CardBox>
|
||||||
<div
|
|
||||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
|
||||||
>
|
|
||||||
<div className="flex justify-between align-center">
|
|
||||||
<div>
|
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
|
||||||
Products
|
|
||||||
</div>
|
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
|
||||||
{products}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<BaseIcon
|
|
||||||
className={`${iconsColor}`}
|
|
||||||
w="w-16"
|
|
||||||
h="h-16"
|
|
||||||
size={48}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
path={'mdiSprayBottle' in icon ? icon['mdiSprayBottle' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>}
|
|
||||||
|
|
||||||
{hasPermission(currentUser, 'READ_SALES_INVOICES') && <Link href={'/sales_invoices/sales_invoices-list'}>
|
|
||||||
<div
|
|
||||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
|
||||||
>
|
|
||||||
<div className="flex justify-between align-center">
|
|
||||||
<div>
|
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
|
||||||
Sales invoices
|
|
||||||
</div>
|
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
|
||||||
{sales_invoices}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<BaseIcon
|
|
||||||
className={`${iconsColor}`}
|
|
||||||
w="w-16"
|
|
||||||
h="h-16"
|
|
||||||
size={48}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
path={'mdiReceipt' in icon ? icon['mdiReceipt' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>}
|
|
||||||
|
|
||||||
{hasPermission(currentUser, 'READ_SALES_INVOICE_ITEMS') && <Link href={'/sales_invoice_items/sales_invoice_items-list'}>
|
|
||||||
<div
|
|
||||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
|
||||||
>
|
|
||||||
<div className="flex justify-between align-center">
|
|
||||||
<div>
|
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
|
||||||
Sales invoice items
|
|
||||||
</div>
|
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
|
||||||
{sales_invoice_items}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<BaseIcon
|
|
||||||
className={`${iconsColor}`}
|
|
||||||
w="w-16"
|
|
||||||
h="h-16"
|
|
||||||
size={48}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
path={'mdiFormatListBulleted' in icon ? icon['mdiFormatListBulleted' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>}
|
|
||||||
|
|
||||||
{hasPermission(currentUser, 'READ_PRICE_CHANGE_LOGS') && <Link href={'/price_change_logs/price_change_logs-list'}>
|
|
||||||
<div
|
|
||||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
|
||||||
>
|
|
||||||
<div className="flex justify-between align-center">
|
|
||||||
<div>
|
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
|
||||||
Price change logs
|
|
||||||
</div>
|
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
|
||||||
{price_change_logs}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<BaseIcon
|
|
||||||
className={`${iconsColor}`}
|
|
||||||
w="w-16"
|
|
||||||
h="h-16"
|
|
||||||
size={48}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
path={'mdiCurrencyUsd' in icon ? icon['mdiCurrencyUsd' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>}
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
Dashboard.getLayout = function getLayout(page: ReactElement) {
|
Dashboard.getLayout = function getLayout(page: ReactElement) {
|
||||||
return <LayoutAuthenticated>{page}</LayoutAuthenticated>
|
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Dashboard
|
export default Dashboard;
|
||||||
|
|||||||
@ -22,23 +22,23 @@ export default function Forgot() {
|
|||||||
const handleSubmit = async (value) => {
|
const handleSubmit = async (value) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const { data: response } = await axios.post('/auth/send-password-reset-email', value);
|
await axios.post('/auth/send-password-reset-email', value);
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
notify('success', 'Please check your email for verification link');
|
notify('success', 'تم إرسال رابط إعادة التعيين إلى بريدك الإلكتروني.');
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
await router.push('/login')
|
await router.push('/login')
|
||||||
}, 3000)
|
}, 3000)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
console.log('error: ', error)
|
console.error('Password reset request failed:', error)
|
||||||
notify('error', 'Something was wrong. Try again')
|
notify('error', 'حدث خطأ. حاول مرة أخرى.')
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Login')}</title>
|
<title>{getPageTitle('استعادة كلمة المرور')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<SectionFullScreen bg='violet'>
|
<SectionFullScreen bg='violet'>
|
||||||
@ -50,7 +50,7 @@ export default function Forgot() {
|
|||||||
onSubmit={(values) => handleSubmit(values)}
|
onSubmit={(values) => handleSubmit(values)}
|
||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<FormField label='Email' help='Please enter your email'>
|
<FormField label='البريد الإلكتروني' help='أدخل بريدك الإلكتروني لإرسال رابط إعادة التعيين'>
|
||||||
<Field name='email' />
|
<Field name='email' />
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -59,12 +59,12 @@ export default function Forgot() {
|
|||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
type='submit'
|
type='submit'
|
||||||
label={loading ? 'Loading...' : 'Submit' }
|
label={loading ? 'جارٍ الإرسال...' : 'إرسال الرابط' }
|
||||||
color='info'
|
color='info'
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
href={'/login'}
|
href={'/login'}
|
||||||
label={'Login'}
|
label={'العودة لتسجيل الدخول'}
|
||||||
color='info'
|
color='info'
|
||||||
/>
|
/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
|
|||||||
@ -1,166 +1,167 @@
|
|||||||
|
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import type { ReactElement } from 'react';
|
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import type { ReactElement } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import BaseButton from '../components/BaseButton';
|
import BaseButton from '../components/BaseButton';
|
||||||
import CardBox from '../components/CardBox';
|
import CardBox from '../components/CardBox';
|
||||||
import SectionFullScreen from '../components/SectionFullScreen';
|
|
||||||
import LayoutGuest from '../layouts/Guest';
|
import LayoutGuest from '../layouts/Guest';
|
||||||
import BaseDivider from '../components/BaseDivider';
|
|
||||||
import BaseButtons from '../components/BaseButtons';
|
|
||||||
import { getPageTitle } from '../config';
|
import { getPageTitle } from '../config';
|
||||||
import { useAppSelector } from '../stores/hooks';
|
|
||||||
import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
|
|
||||||
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
|
|
||||||
|
|
||||||
|
const highlights = [
|
||||||
|
{
|
||||||
|
title: 'كاشير سريع',
|
||||||
|
text: 'بحث فوري عن المنتجات، إضافة للفاتورة بضغطة واحدة، وحساب مباشر للإجمالي.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'تسعير حسب الدولار',
|
||||||
|
text: 'حفظ سعر الدولار اليومي وتطبيق تحديث جماعي على أسعار البيع مع إمكانية الرجوع.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'تقارير يومية',
|
||||||
|
text: 'متابعة مبيعات اليوم، الأرباح، وعدد الفواتير مع تفاصيل واضحة لكل فاتورة.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default function Starter() {
|
const quickLinks = [
|
||||||
const [illustrationImage, setIllustrationImage] = useState({
|
{ href: '/login', label: 'تسجيل الدخول' },
|
||||||
src: undefined,
|
{ href: '/dashboard', label: 'واجهة الإدارة' },
|
||||||
photographer: undefined,
|
{ href: '/cashier', label: 'شاشة الكاشير' },
|
||||||
photographer_url: undefined,
|
{ href: '/products/products-list', label: 'المنتجات' },
|
||||||
})
|
];
|
||||||
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
|
|
||||||
const [contentType, setContentType] = useState('image');
|
|
||||||
const [contentPosition, setContentPosition] = useState('right');
|
|
||||||
const textColor = useAppSelector((state) => state.style.linkColor);
|
|
||||||
|
|
||||||
const title = 'Multi-Client Detergents POS'
|
|
||||||
|
|
||||||
// Fetch Pexels image/video
|
|
||||||
useEffect(() => {
|
|
||||||
async function fetchData() {
|
|
||||||
const image = await getPexelsImage();
|
|
||||||
const video = await getPexelsVideo();
|
|
||||||
setIllustrationImage(image);
|
|
||||||
setIllustrationVideo(video);
|
|
||||||
}
|
|
||||||
fetchData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const imageBlock = (image) => (
|
|
||||||
<div
|
|
||||||
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
|
|
||||||
style={{
|
|
||||||
backgroundImage: `${
|
|
||||||
image
|
|
||||||
? `url(${image?.src?.original})`
|
|
||||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
|
||||||
}`,
|
|
||||||
backgroundSize: 'cover',
|
|
||||||
backgroundPosition: 'left center',
|
|
||||||
backgroundRepeat: 'no-repeat',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className='flex justify-center w-full bg-blue-300/20'>
|
|
||||||
<a
|
|
||||||
className='text-[8px]'
|
|
||||||
href={image?.photographer_url}
|
|
||||||
target='_blank'
|
|
||||||
rel='noreferrer'
|
|
||||||
>
|
|
||||||
Photo by {image?.photographer} on Pexels
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const videoBlock = (video) => {
|
|
||||||
if (video?.video_files?.length > 0) {
|
|
||||||
return (
|
|
||||||
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
|
|
||||||
<video
|
|
||||||
className='absolute top-0 left-0 w-full h-full object-cover'
|
|
||||||
autoPlay
|
|
||||||
loop
|
|
||||||
muted
|
|
||||||
>
|
|
||||||
<source src={video?.video_files[0]?.link} type='video/mp4'/>
|
|
||||||
Your browser does not support the video tag.
|
|
||||||
</video>
|
|
||||||
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
|
|
||||||
<a
|
|
||||||
className='text-[8px]'
|
|
||||||
href={video?.user?.url}
|
|
||||||
target='_blank'
|
|
||||||
rel='noreferrer'
|
|
||||||
>
|
|
||||||
Video by {video.user.name} on Pexels
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
export default function HomePage() {
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
style={
|
|
||||||
contentPosition === 'background'
|
|
||||||
? {
|
|
||||||
backgroundImage: `${
|
|
||||||
illustrationImage
|
|
||||||
? `url(${illustrationImage.src?.original})`
|
|
||||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
|
||||||
}`,
|
|
||||||
backgroundSize: 'cover',
|
|
||||||
backgroundPosition: 'left center',
|
|
||||||
backgroundRepeat: 'no-repeat',
|
|
||||||
}
|
|
||||||
: {}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Starter Page')}</title>
|
<title>{getPageTitle('نظام إدارة محل منظفات')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
|
<div className="app-rtl min-h-screen bg-[radial-gradient(circle_at_top,_rgba(16,185,129,0.16),_transparent_32%),radial-gradient(circle_at_bottom_left,_rgba(14,165,233,0.16),_transparent_28%),linear-gradient(180deg,#f8fafc_0%,#ffffff_65%)]" dir="rtl">
|
||||||
|
<header className="sticky top-0 z-20 border-b border-white/70 bg-white/80 backdrop-blur-xl">
|
||||||
|
<div className="mx-auto flex max-w-7xl items-center justify-between gap-4 px-6 py-4">
|
||||||
|
<div>
|
||||||
|
<div className="text-xl font-extrabold text-slate-900">منظفات برو</div>
|
||||||
|
<div className="text-sm text-slate-500">منصة عربية حديثة لإدارة المبيعات والمخزون</div>
|
||||||
|
</div>
|
||||||
|
<nav className="flex flex-wrap items-center gap-2">
|
||||||
|
{quickLinks.map((link) => (
|
||||||
|
<Link
|
||||||
|
key={link.href}
|
||||||
|
href={link.href}
|
||||||
|
className="rounded-full px-4 py-2 text-sm font-bold text-slate-700 transition hover:bg-slate-100 hover:text-emerald-700"
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
<SectionFullScreen bg='violet'>
|
<main className="mx-auto flex max-w-7xl flex-col gap-10 px-6 py-10 lg:py-16">
|
||||||
<div
|
<section className="grid gap-8 lg:grid-cols-[1.1fr,0.9fr] lg:items-center">
|
||||||
className={`flex ${
|
<div className="space-y-6">
|
||||||
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
|
<span className="inline-flex rounded-full border border-emerald-100 bg-emerald-50 px-4 py-2 text-sm font-bold text-emerald-700">
|
||||||
} min-h-screen w-full`}
|
موقع تعريفي + نظام كاشير عربي متعدد العملاء
|
||||||
>
|
</span>
|
||||||
{contentType === 'image' && contentPosition !== 'background'
|
<div className="space-y-4">
|
||||||
? imageBlock(illustrationImage)
|
<h1 className="text-4xl font-extrabold leading-tight text-slate-950 lg:text-6xl">
|
||||||
: null}
|
إدارة مبيعات محل المنظفات بشكل أسرع وأوضح وأجمل
|
||||||
{contentType === 'video' && contentPosition !== 'background'
|
</h1>
|
||||||
? videoBlock(illustrationVideo)
|
<p className="max-w-2xl text-lg leading-8 text-slate-600">
|
||||||
: null}
|
واجهة عربية بالكامل، تصميم مريح للعين، شاشة كاشير مناسبة للمحل التجاري، وتسعير ذكي حسب الدولار مع تقارير يومية وأرباح دقيقة.
|
||||||
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
|
</p>
|
||||||
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
|
</div>
|
||||||
<CardBoxComponentTitle title="Welcome to your Multi-Client Detergents POS app!"/>
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
|
<BaseButton href="/login" color="success" label="ابدأ من لوحة الإدارة" className="!px-6 !py-3 text-base font-bold" />
|
||||||
<div className="space-y-3">
|
<BaseButton href="/cashier" color="info" label="جرّب شاشة الكاشير" className="!px-6 !py-3 text-base font-bold" />
|
||||||
<p className='text-center text-gray-500'>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
|
</div>
|
||||||
<p className='text-center text-gray-500'>For guides and documentation please check
|
<div className="grid gap-4 sm:grid-cols-3">
|
||||||
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
|
<div className="rounded-3xl border border-white bg-white/90 p-5 shadow-lg shadow-emerald-100/70">
|
||||||
|
<div className="text-sm font-bold text-slate-500">اللغة والاتجاه</div>
|
||||||
|
<div className="mt-2 text-2xl font-extrabold text-slate-900">عربي + RTL</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-3xl border border-white bg-white/90 p-5 shadow-lg shadow-sky-100/70">
|
||||||
|
<div className="text-sm font-bold text-slate-500">التوسع</div>
|
||||||
|
<div className="mt-2 text-2xl font-extrabold text-slate-900">+200 منتج</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-3xl border border-white bg-white/90 p-5 shadow-lg shadow-slate-100/80">
|
||||||
|
<div className="text-sm font-bold text-slate-500">المستخدمون</div>
|
||||||
|
<div className="mt-2 text-2xl font-extrabold text-slate-900">عدة عملاء</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BaseButtons>
|
<CardBox className="border-0 bg-slate-950 text-white shadow-2xl shadow-sky-100/60">
|
||||||
<BaseButton
|
<div className="space-y-5 p-2">
|
||||||
href='/login'
|
<div className="flex items-center justify-between">
|
||||||
label='Login'
|
<div>
|
||||||
color='info'
|
<div className="text-sm font-bold text-slate-400">المشهد الأول في النظام</div>
|
||||||
className='w-full'
|
<div className="mt-1 text-2xl font-extrabold">شاشة كاشير عصرية للمحل</div>
|
||||||
/>
|
</div>
|
||||||
|
<span className="rounded-full bg-white/10 px-3 py-1 text-xs font-bold text-sky-200">MVP جاهز</span>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
|
<div className="rounded-3xl bg-white/5 p-4 ring-1 ring-white/10">
|
||||||
|
<div className="text-sm text-slate-400">بحث فوري</div>
|
||||||
|
<div className="mt-2 text-lg font-bold">اقتراحات مباشرة بدون إعادة تحميل</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-3xl bg-white/5 p-4 ring-1 ring-white/10">
|
||||||
|
<div className="text-sm text-slate-400">فاتورة سريعة</div>
|
||||||
|
<div className="mt-2 text-lg font-bold">إجمالي وربح وعدد قطع في نفس اللحظة</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-3xl bg-white/5 p-4 ring-1 ring-white/10">
|
||||||
|
<div className="text-sm text-slate-400">سعر الدولار</div>
|
||||||
|
<div className="mt-2 text-lg font-bold">حفظ السعر اليومي + تطبيق جماعي + استرجاع</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-3xl bg-white/5 p-4 ring-1 ring-white/10">
|
||||||
|
<div className="text-sm text-slate-400">تقارير اليوم</div>
|
||||||
|
<div className="mt-2 text-lg font-bold">مبيعات وأرباح وفواتير اليوم من نفس الشاشة</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-[28px] bg-gradient-to-l from-emerald-500 to-sky-500 p-5 text-slate-950">
|
||||||
|
<div className="text-sm font-bold">الوصول السريع</div>
|
||||||
|
<div className="mt-2 text-2xl font-extrabold">لوحة الإدارة ما زالت متاحة بالكامل</div>
|
||||||
|
<p className="mt-2 text-sm leading-7 text-slate-900/80">
|
||||||
|
من هنا يمكنك دخول الواجهة الإدارية الحالية لإدارة المنتجات، الأقسام، المحلات، الفواتير، والمستخدمين بدون حذف أي شيء من البنية الجاهزة.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
</section>
|
||||||
|
|
||||||
</BaseButtons>
|
<section className="grid gap-4 lg:grid-cols-3">
|
||||||
</CardBox>
|
{highlights.map((item) => (
|
||||||
</div>
|
<CardBox key={item.title} className="border-0 bg-white/90 shadow-lg shadow-slate-100/80">
|
||||||
</div>
|
<div className="space-y-3">
|
||||||
</SectionFullScreen>
|
<div className="inline-flex rounded-full bg-slate-100 px-3 py-1 text-xs font-bold text-slate-600">ميزة أساسية</div>
|
||||||
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
|
<h2 className="text-2xl font-extrabold text-slate-900">{item.title}</h2>
|
||||||
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
|
<p className="leading-8 text-slate-600">{item.text}</p>
|
||||||
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
|
</div>
|
||||||
Privacy Policy
|
</CardBox>
|
||||||
</Link>
|
))}
|
||||||
</div>
|
</section>
|
||||||
|
|
||||||
</div>
|
<section className="rounded-[32px] border border-slate-100 bg-white/90 p-8 shadow-xl shadow-slate-100/80">
|
||||||
|
<div className="grid gap-6 lg:grid-cols-[1fr,auto] lg:items-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-bold text-emerald-600">ماذا ستجد الآن؟</div>
|
||||||
|
<h2 className="mt-2 text-3xl font-extrabold text-slate-950">أول شريحة MVP تعمل من البداية للنهاية</h2>
|
||||||
|
<p className="mt-3 max-w-3xl text-lg leading-8 text-slate-600">
|
||||||
|
صفحة عامة جميلة، رابط مباشر للوحة الإدارة، وشاشة كاشير عربية تجمع البيع السريع مع تقرير اليوم وأدوات تحديث الأسعار حسب الدولار.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
<BaseButton href="/dashboard" color="info" label="فتح واجهة الإدارة" className="!px-6 !py-3 text-base font-bold" />
|
||||||
|
<BaseButton href="/sales_invoices/sales_invoices-list" color="success" label="عرض الفواتير" className="!px-6 !py-3 text-base font-bold" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Starter.getLayout = function getLayout(page: ReactElement) {
|
HomePage.getLayout = function getLayout(page: ReactElement) {
|
||||||
return <LayoutGuest>{page}</LayoutGuest>;
|
return <LayoutGuest>{page}</LayoutGuest>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export default function Login() {
|
|||||||
password: '0897e59a',
|
password: '0897e59a',
|
||||||
remember: true })
|
remember: true })
|
||||||
|
|
||||||
const title = 'Multi-Client Detergents POS'
|
const title = 'نظام مبيعات محل المنظفات'
|
||||||
|
|
||||||
// Fetch Pexels image/video
|
// Fetch Pexels image/video
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
@ -109,8 +109,7 @@ export default function Login() {
|
|||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
}}>
|
}}>
|
||||||
<div className="flex justify-center w-full bg-blue-300/20">
|
<div className="flex justify-center w-full bg-blue-300/20">
|
||||||
<a className="text-[8px]" href={image?.photographer_url} target="_blank" rel="noreferrer">Photo
|
<a className="text-[8px]" href={image?.photographer_url} target="_blank" rel="noreferrer">الصورة بعدسة {image?.photographer} عبر Pexels</a>
|
||||||
by {image?.photographer} on Pexels</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -126,7 +125,7 @@ export default function Login() {
|
|||||||
muted
|
muted
|
||||||
>
|
>
|
||||||
<source src={video.video_files[0]?.link} type='video/mp4'/>
|
<source src={video.video_files[0]?.link} type='video/mp4'/>
|
||||||
Your browser does not support the video tag.
|
متصفحك لا يدعم تشغيل الفيديو.
|
||||||
</video>
|
</video>
|
||||||
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
|
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
|
||||||
<a
|
<a
|
||||||
@ -135,7 +134,7 @@ export default function Login() {
|
|||||||
target='_blank'
|
target='_blank'
|
||||||
rel='noreferrer'
|
rel='noreferrer'
|
||||||
>
|
>
|
||||||
Video by {video.user.name} on Pexels
|
الفيديو بواسطة {video.user.name} عبر Pexels
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>)
|
</div>)
|
||||||
@ -154,7 +153,7 @@ export default function Login() {
|
|||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
} : {}}>
|
} : {}}>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Login')}</title>
|
<title>{getPageTitle('تسجيل الدخول')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<SectionFullScreen bg='violet'>
|
<SectionFullScreen bg='violet'>
|
||||||
@ -170,25 +169,25 @@ export default function Login() {
|
|||||||
<div className='flex flex-row text-gray-500 justify-between'>
|
<div className='flex flex-row text-gray-500 justify-between'>
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<p className='mb-2'>Use{' '}
|
<p className='mb-2'>استخدم{' '}
|
||||||
<code className={`cursor-pointer ${textColor} `}
|
<code className={`cursor-pointer ${textColor} `}
|
||||||
data-password="0897e59a"
|
data-password="0897e59a"
|
||||||
onClick={(e) => setLogin(e.target)}>super_admin@flatlogic.com</code>{' / '}
|
onClick={(e) => setLogin(e.target)}>super_admin@flatlogic.com</code>{' / '}
|
||||||
<code className={`${textColor}`}>0897e59a</code>{' / '}
|
<code className={`${textColor}`}>0897e59a</code>{' / '}
|
||||||
to login as Super Admin</p>
|
للدخول كمدير عام</p>
|
||||||
|
|
||||||
<p className='mb-2'>Use{' '}
|
<p className='mb-2'>استخدم{' '}
|
||||||
<code className={`cursor-pointer ${textColor} `}
|
<code className={`cursor-pointer ${textColor} `}
|
||||||
data-password="0897e59a"
|
data-password="0897e59a"
|
||||||
onClick={(e) => setLogin(e.target)}>admin@flatlogic.com</code>{' / '}
|
onClick={(e) => setLogin(e.target)}>admin@flatlogic.com</code>{' / '}
|
||||||
<code className={`${textColor}`}>0897e59a</code>{' / '}
|
<code className={`${textColor}`}>0897e59a</code>{' / '}
|
||||||
to login as Admin</p>
|
للدخول كمدير</p>
|
||||||
<p>Use <code
|
<p>استخدم <code
|
||||||
className={`cursor-pointer ${textColor} `}
|
className={`cursor-pointer ${textColor} `}
|
||||||
data-password="c72eaa09f2c5"
|
data-password="c72eaa09f2c5"
|
||||||
onClick={(e) => setLogin(e.target)}>client@hello.com</code>{' / '}
|
onClick={(e) => setLogin(e.target)}>client@hello.com</code>{' / '}
|
||||||
<code className={`${textColor}`}>c72eaa09f2c5</code>{' / '}
|
<code className={`${textColor}`}>c72eaa09f2c5</code>{' / '}
|
||||||
to login as User</p>
|
للدخول كمستخدم</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<BaseIcon
|
<BaseIcon
|
||||||
@ -210,15 +209,15 @@ export default function Login() {
|
|||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<FormField
|
<FormField
|
||||||
label='Login'
|
label='البريد الإلكتروني'
|
||||||
help='Please enter your login'>
|
help='أدخل بريدك الإلكتروني'>
|
||||||
<Field name='email' />
|
<Field name='email' />
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<div className='relative'>
|
<div className='relative'>
|
||||||
<FormField
|
<FormField
|
||||||
label='Password'
|
label='كلمة المرور'
|
||||||
help='Please enter your password'>
|
help='أدخل كلمة المرور'>
|
||||||
<Field name='password' type={showPassword ? 'text' : 'password'} />
|
<Field name='password' type={showPassword ? 'text' : 'password'} />
|
||||||
</FormField>
|
</FormField>
|
||||||
<div
|
<div
|
||||||
@ -234,12 +233,12 @@ export default function Login() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={'flex justify-between'}>
|
<div className={'flex justify-between'}>
|
||||||
<FormCheckRadio type='checkbox' label='Remember'>
|
<FormCheckRadio type='checkbox' label='تذكرني'>
|
||||||
<Field type='checkbox' name='remember' />
|
<Field type='checkbox' name='remember' />
|
||||||
</FormCheckRadio>
|
</FormCheckRadio>
|
||||||
|
|
||||||
<Link className={`${textColor} text-blue-600`} href={'/forgot'}>
|
<Link className={`${textColor} text-blue-600`} href={'/forgot'}>
|
||||||
Forgot password?
|
نسيت كلمة المرور؟
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -249,16 +248,16 @@ export default function Login() {
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className={'w-full'}
|
className={'w-full'}
|
||||||
type='submit'
|
type='submit'
|
||||||
label={isFetching ? 'Loading...' : 'Login'}
|
label={isFetching ? 'جارٍ تسجيل الدخول...' : 'تسجيل الدخول'}
|
||||||
color='info'
|
color='info'
|
||||||
disabled={isFetching}
|
disabled={isFetching}
|
||||||
/>
|
/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
<br />
|
<br />
|
||||||
<p className={'text-center'}>
|
<p className={'text-center'}>
|
||||||
Don’t have an account yet?{' '}
|
ليس لديك حساب بعد؟{' '}
|
||||||
<Link className={`${textColor}`} href={'/register'}>
|
<Link className={`${textColor}`} href={'/register'}>
|
||||||
New Account
|
إنشاء حساب جديد
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</Form>
|
</Form>
|
||||||
@ -268,9 +267,9 @@ export default function Login() {
|
|||||||
</div>
|
</div>
|
||||||
</SectionFullScreen>
|
</SectionFullScreen>
|
||||||
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
|
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
|
||||||
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. © All rights reserved</p>
|
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. جميع الحقوق محفوظة</p>
|
||||||
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
|
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
|
||||||
Privacy Policy
|
سياسة الخصوصية
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
|
|||||||
@ -109,10 +109,10 @@ const EditOrganizations = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Edit organizations')}</title>
|
<title>{getPageTitle('تعديل منظمة')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'Edit organizations'} main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'تعديل منظمة'} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -164,9 +164,9 @@ const EditOrganizations = () => {
|
|||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Submit" />
|
<BaseButton type="submit" color="info" label="حفظ" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="إعادة تعيين" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/organizations/organizations-list')}/>
|
<BaseButton type='reset' color='danger' outline label='إلغاء' onClick={() => router.push('/organizations/organizations-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@ -106,10 +106,10 @@ const EditOrganizationsPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Edit organizations')}</title>
|
<title>{getPageTitle('تعديل منظمة')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'Edit organizations'} main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'تعديل منظمة'} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -161,9 +161,9 @@ const EditOrganizationsPage = () => {
|
|||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Submit" />
|
<BaseButton type="submit" color="info" label="حفظ" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="إعادة تعيين" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/organizations/organizations-list')}/>
|
<BaseButton type='reset' color='danger' outline label='إلغاء' onClick={() => router.push('/organizations/organizations-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@ -86,28 +86,28 @@ const OrganizationsTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Organizations')}</title>
|
<title>{getPageTitle('المنظمات')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Organizations" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="المنظمات" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/organizations/organizations-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/organizations/organizations-new'} color='info' label='إضافة منظمة'/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label='تصفية'
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getOrganizationsCSV} />
|
<BaseButton className={'mr-3'} color='info' label='تنزيل CSV' onClick={getOrganizationsCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label='رفع CSV'
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -62,10 +62,10 @@ const OrganizationsNew = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('New Item')}</title>
|
<title>{getPageTitle('إضافة منظمة')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Item" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="إضافة منظمة" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -116,9 +116,9 @@ const OrganizationsNew = () => {
|
|||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Submit" />
|
<BaseButton type="submit" color="info" label="حفظ" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="إعادة تعيين" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/organizations/organizations-list')}/>
|
<BaseButton type='reset' color='danger' outline label='إلغاء' onClick={() => router.push('/organizations/organizations-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@ -86,28 +86,28 @@ const OrganizationsTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Organizations')}</title>
|
<title>{getPageTitle('المنظمات')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Organizations" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="المنظمات" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/organizations/organizations-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/organizations/organizations-new'} color='info' label='إضافة منظمة'/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label='تصفية'
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getOrganizationsCSV} />
|
<BaseButton className={'mr-3'} color='info' label='تنزيل CSV' onClick={getOrganizationsCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label='رفع CSV'
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -33,11 +33,6 @@ const OrganizationsView = () => {
|
|||||||
|
|
||||||
const { id } = router.query;
|
const { id } = router.query;
|
||||||
|
|
||||||
function removeLastCharacter(str) {
|
|
||||||
console.log(str,`str`)
|
|
||||||
return str.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetch({ id }));
|
dispatch(fetch({ id }));
|
||||||
}, [dispatch, id]);
|
}, [dispatch, id]);
|
||||||
@ -46,13 +41,13 @@ const OrganizationsView = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('View organizations')}</title>
|
<title>{getPageTitle('عرض منظمة')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={removeLastCharacter('View organizations')} main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="عرض منظمة" main>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Edit'
|
label='تعديل'
|
||||||
href={`/organizations/organizations-edit/?id=${id}`}
|
href={`/organizations/organizations-edit/?id=${id}`}
|
||||||
/>
|
/>
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
@ -389,7 +384,7 @@ const OrganizationsView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>رمزالمنتج</th>
|
<th>رمز المنتج</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -401,11 +396,11 @@ const OrganizationsView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>سعرالبيع</th>
|
<th>سعر البيع</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>سعرالبيعالسابق</th>
|
<th>سعر البيعالسابق</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -427,7 +422,7 @@ const OrganizationsView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>متاحللبيع</th>
|
<th>متاح للبيع</th>
|
||||||
|
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
@ -535,11 +530,11 @@ const OrganizationsView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>رقمالفاتورة</th>
|
<th>رقم الفاتورة</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>تاريخووقتالبيع</th>
|
<th>تاريخ ووقت البيع</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -547,7 +542,7 @@ const OrganizationsView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>الإجماليقبلالخصم</th>
|
<th>الإجمالي قبل الخصم</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -555,19 +550,19 @@ const OrganizationsView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>الإجماليالنهائي</th>
|
<th>الإجمالي النهائي</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>إجماليالتكلفة</th>
|
<th>إجمالي التكلفة</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>إجماليالربح</th>
|
<th>إجمالي الربح</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>طريقةالدفع</th>
|
<th>طريقة الدفع</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -683,7 +678,7 @@ const OrganizationsView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<th>سعرالبيعوقتالبيع</th>
|
<th>سعر البيعوقتالبيع</th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -846,7 +841,7 @@ const OrganizationsView = () => {
|
|||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Back'
|
label='رجوع'
|
||||||
onClick={() => router.push('/organizations/organizations-list')}
|
onClick={() => router.push('/organizations/organizations-list')}
|
||||||
/>
|
/>
|
||||||
</CardBox>
|
</CardBox>
|
||||||
|
|||||||
@ -109,10 +109,10 @@ const EditPermissions = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Edit permissions')}</title>
|
<title>{getPageTitle('تعديل صلاحية')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'Edit permissions'} main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'تعديل صلاحية'} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -164,9 +164,9 @@ const EditPermissions = () => {
|
|||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Submit" />
|
<BaseButton type="submit" color="info" label="حفظ" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="إعادة تعيين" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/permissions/permissions-list')}/>
|
<BaseButton type='reset' color='danger' outline label='إلغاء' onClick={() => router.push('/permissions/permissions-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@ -106,10 +106,10 @@ const EditPermissionsPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Edit permissions')}</title>
|
<title>{getPageTitle('تعديل صلاحية')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'Edit permissions'} main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'تعديل صلاحية'} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -161,9 +161,9 @@ const EditPermissionsPage = () => {
|
|||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Submit" />
|
<BaseButton type="submit" color="info" label="حفظ" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="إعادة تعيين" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/permissions/permissions-list')}/>
|
<BaseButton type='reset' color='danger' outline label='إلغاء' onClick={() => router.push('/permissions/permissions-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@ -86,28 +86,28 @@ const PermissionsTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Permissions')}</title>
|
<title>{getPageTitle('الصلاحيات')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Permissions" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="الصلاحيات" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/permissions/permissions-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/permissions/permissions-new'} color='info' label='إضافة صلاحية'/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label='تصفية'
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getPermissionsCSV} />
|
<BaseButton className={'mr-3'} color='info' label='تنزيل CSV' onClick={getPermissionsCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label='رفع CSV'
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -62,10 +62,10 @@ const PermissionsNew = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('New Item')}</title>
|
<title>{getPageTitle('إضافة صلاحية')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Item" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="إضافة صلاحية" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -116,9 +116,9 @@ const PermissionsNew = () => {
|
|||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Submit" />
|
<BaseButton type="submit" color="info" label="حفظ" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="إعادة تعيين" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/permissions/permissions-list')}/>
|
<BaseButton type='reset' color='danger' outline label='إلغاء' onClick={() => router.push('/permissions/permissions-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@ -86,28 +86,28 @@ const PermissionsTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Permissions')}</title>
|
<title>{getPageTitle('الصلاحيات')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Permissions" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="الصلاحيات" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/permissions/permissions-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/permissions/permissions-new'} color='info' label='إضافة صلاحية'/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label='تصفية'
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getPermissionsCSV} />
|
<BaseButton className={'mr-3'} color='info' label='تنزيل CSV' onClick={getPermissionsCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label='رفع CSV'
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -33,11 +33,6 @@ const PermissionsView = () => {
|
|||||||
|
|
||||||
const { id } = router.query;
|
const { id } = router.query;
|
||||||
|
|
||||||
function removeLastCharacter(str) {
|
|
||||||
console.log(str,`str`)
|
|
||||||
return str.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetch({ id }));
|
dispatch(fetch({ id }));
|
||||||
}, [dispatch, id]);
|
}, [dispatch, id]);
|
||||||
@ -46,13 +41,13 @@ const PermissionsView = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('View permissions')}</title>
|
<title>{getPageTitle('عرض صلاحية')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={removeLastCharacter('View permissions')} main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="عرض صلاحية" main>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Edit'
|
label='تعديل'
|
||||||
href={`/permissions/permissions-edit/?id=${id}`}
|
href={`/permissions/permissions-edit/?id=${id}`}
|
||||||
/>
|
/>
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
@ -107,7 +102,7 @@ const PermissionsView = () => {
|
|||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Back'
|
label='رجوع'
|
||||||
onClick={() => router.push('/permissions/permissions-list')}
|
onClick={() => router.push('/permissions/permissions-list')}
|
||||||
/>
|
/>
|
||||||
</CardBox>
|
</CardBox>
|
||||||
|
|||||||
@ -305,10 +305,10 @@ const EditPrice_change_logs = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Edit price_change_logs')}</title>
|
<title>{getPageTitle('تعديل سجل تغيير السعر')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'Edit price_change_logs'} main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'تعديل سجل تغيير السعر'} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -706,9 +706,9 @@ const EditPrice_change_logs = () => {
|
|||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Submit" />
|
<BaseButton type="submit" color="info" label="حفظ" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="إعادة تعيين" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/price_change_logs/price_change_logs-list')}/>
|
<BaseButton type='reset' color='danger' outline label='إلغاء' onClick={() => router.push('/price_change_logs/price_change_logs-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@ -302,10 +302,10 @@ const EditPrice_change_logsPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Edit price_change_logs')}</title>
|
<title>{getPageTitle('تعديل سجل تغيير السعر')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'Edit price_change_logs'} main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'تعديل سجل تغيير السعر'} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -703,9 +703,9 @@ const EditPrice_change_logsPage = () => {
|
|||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Submit" />
|
<BaseButton type="submit" color="info" label="حفظ" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="إعادة تعيين" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/price_change_logs/price_change_logs-list')}/>
|
<BaseButton type='reset' color='danger' outline label='إلغاء' onClick={() => router.push('/price_change_logs/price_change_logs-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@ -94,28 +94,28 @@ const Price_change_logsTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Price_change_logs')}</title>
|
<title>{getPageTitle('سجلات تغيير السعر')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Price_change_logs" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="سجلات تغيير السعر" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/price_change_logs/price_change_logs-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/price_change_logs/price_change_logs-new'} color='info' label='إضافة سجل تغيير السعر'/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label='تصفية'
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getPrice_change_logsCSV} />
|
<BaseButton className={'mr-3'} color='info' label='تنزيل CSV' onClick={getPrice_change_logsCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label='رفع CSV'
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -175,10 +175,10 @@ const Price_change_logsNew = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('New Item')}</title>
|
<title>{getPageTitle('إضافة سجل تغيير السعر')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Item" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="إضافة سجل تغيير السعر" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -460,9 +460,9 @@ const Price_change_logsNew = () => {
|
|||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Submit" />
|
<BaseButton type="submit" color="info" label="حفظ" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="إعادة تعيين" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/price_change_logs/price_change_logs-list')}/>
|
<BaseButton type='reset' color='danger' outline label='إلغاء' onClick={() => router.push('/price_change_logs/price_change_logs-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@ -94,28 +94,28 @@ const Price_change_logsTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Price_change_logs')}</title>
|
<title>{getPageTitle('سجلات تغيير السعر')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Price_change_logs" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="سجلات تغيير السعر" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/price_change_logs/price_change_logs-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/price_change_logs/price_change_logs-new'} color='info' label='إضافة سجل تغيير السعر'/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label='تصفية'
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getPrice_change_logsCSV} />
|
<BaseButton className={'mr-3'} color='info' label='تنزيل CSV' onClick={getPrice_change_logsCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label='رفع CSV'
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -33,11 +33,6 @@ const Price_change_logsView = () => {
|
|||||||
|
|
||||||
const { id } = router.query;
|
const { id } = router.query;
|
||||||
|
|
||||||
function removeLastCharacter(str) {
|
|
||||||
console.log(str,`str`)
|
|
||||||
return str.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetch({ id }));
|
dispatch(fetch({ id }));
|
||||||
}, [dispatch, id]);
|
}, [dispatch, id]);
|
||||||
@ -46,13 +41,13 @@ const Price_change_logsView = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('View price_change_logs')}</title>
|
<title>{getPageTitle('عرض سجل تغيير السعر')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={removeLastCharacter('View price_change_logs')} main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="عرض سجل تغيير السعر" main>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Edit'
|
label='تعديل'
|
||||||
href={`/price_change_logs/price_change_logs-edit/?id=${id}`}
|
href={`/price_change_logs/price_change_logs-edit/?id=${id}`}
|
||||||
/>
|
/>
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
@ -91,7 +86,7 @@ const Price_change_logsView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<p>{price_change_logs?.shop?.shop_name ?? 'No data'}</p>
|
<p>{price_change_logs?.shop?.shop_name ?? 'لا توجد بيانات'}</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -139,7 +134,7 @@ const Price_change_logsView = () => {
|
|||||||
<p className={'block font-bold mb-2'}>تمالتغييربواسطة</p>
|
<p className={'block font-bold mb-2'}>تمالتغييربواسطة</p>
|
||||||
|
|
||||||
|
|
||||||
<p>{price_change_logs?.changed_by?.firstName ?? 'No data'}</p>
|
<p>{price_change_logs?.changed_by?.firstName ?? 'لا توجد بيانات'}</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -228,7 +223,7 @@ const Price_change_logsView = () => {
|
|||||||
|
|
||||||
<div className={'mb-4'}>
|
<div className={'mb-4'}>
|
||||||
<p className={'block font-bold mb-2'}>نوعالتغيير</p>
|
<p className={'block font-bold mb-2'}>نوعالتغيير</p>
|
||||||
<p>{price_change_logs?.change_type ?? 'No data'}</p>
|
<p>{price_change_logs?.change_type ?? 'لا توجد بيانات'}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -252,7 +247,7 @@ const Price_change_logsView = () => {
|
|||||||
|
|
||||||
<div className={'mb-4'}>
|
<div className={'mb-4'}>
|
||||||
<p className={'block font-bold mb-2'}>سعرالدولارقبل</p>
|
<p className={'block font-bold mb-2'}>سعرالدولارقبل</p>
|
||||||
<p>{price_change_logs?.usd_rate_before || 'No data'}</p>
|
<p>{price_change_logs?.usd_rate_before || 'لا توجد بيانات'}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -284,7 +279,7 @@ const Price_change_logsView = () => {
|
|||||||
|
|
||||||
<div className={'mb-4'}>
|
<div className={'mb-4'}>
|
||||||
<p className={'block font-bold mb-2'}>سعرالدولاربعد</p>
|
<p className={'block font-bold mb-2'}>سعرالدولاربعد</p>
|
||||||
<p>{price_change_logs?.usd_rate_after || 'No data'}</p>
|
<p>{price_change_logs?.usd_rate_after || 'لا توجد بيانات'}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -368,7 +363,7 @@ const Price_change_logsView = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<p>{price_change_logs?.organizations?.name ?? 'No data'}</p>
|
<p>{price_change_logs?.organizations?.name ?? 'لا توجد بيانات'}</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -410,7 +405,7 @@ const Price_change_logsView = () => {
|
|||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Back'
|
label='رجوع'
|
||||||
onClick={() => router.push('/price_change_logs/price_change_logs-list')}
|
onClick={() => router.push('/price_change_logs/price_change_logs-list')}
|
||||||
/>
|
/>
|
||||||
</CardBox>
|
</CardBox>
|
||||||
|
|||||||
@ -501,10 +501,10 @@ const EditProducts = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Edit products')}</title>
|
<title>{getPageTitle('تعديل المنتج')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'Edit products'} main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'تعديل المنتج'} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -646,11 +646,11 @@ const EditProducts = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="اسمالمنتج"
|
label="اسم المنتج"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="product_name"
|
name="product_name"
|
||||||
placeholder="اسمالمنتج"
|
placeholder="اسم المنتج"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -683,11 +683,11 @@ const EditProducts = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="رمزالمنتج"
|
label="رمز المنتج"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="sku"
|
name="sku"
|
||||||
placeholder="رمزالمنتج"
|
placeholder="رمز المنتج"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -763,12 +763,12 @@ const EditProducts = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="سعرالتكلفةالحقيقي"
|
label="سعر التكلفة"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="cost_price"
|
name="cost_price"
|
||||||
placeholder="سعرالتكلفةالحقيقي"
|
placeholder="سعر التكلفة"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -801,12 +801,12 @@ const EditProducts = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="سعرالبيع"
|
label="سعر البيع"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="sale_price"
|
name="sale_price"
|
||||||
placeholder="سعرالبيع"
|
placeholder="سعر البيع"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -839,12 +839,12 @@ const EditProducts = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="سعرالبيعالسابق"
|
label="سعر البيع السابق"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="sale_price_backup"
|
name="sale_price_backup"
|
||||||
placeholder="سعرالبيعالسابق"
|
placeholder="سعر البيع السابق"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -877,12 +877,12 @@ const EditProducts = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="سعرالتكلفةالسابق"
|
label="سعر التكلفة السابق"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="cost_price_backup"
|
name="cost_price_backup"
|
||||||
placeholder="سعرالتكلفةالسابق"
|
placeholder="سعر التكلفة السابق"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -915,12 +915,12 @@ const EditProducts = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="السعربالدولار"
|
label="السعر بالدولار"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="usd_price"
|
name="usd_price"
|
||||||
placeholder="السعربالدولار"
|
placeholder="السعر بالدولار"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -953,12 +953,12 @@ const EditProducts = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="الكميةبالمخزون"
|
label="الكمية بالمخزون"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="stock_quantity"
|
name="stock_quantity"
|
||||||
placeholder="الكميةبالمخزون"
|
placeholder="الكمية بالمخزون"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -991,12 +991,12 @@ const EditProducts = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="حدالتنبيهلنقصالمخزون"
|
label="حد التنبيه لنقص المخزون"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="low_stock_threshold"
|
name="low_stock_threshold"
|
||||||
placeholder="حدالتنبيهلنقصالمخزون"
|
placeholder="حد التنبيه لنقص المخزون"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -1046,7 +1046,7 @@ const EditProducts = () => {
|
|||||||
|
|
||||||
<FormField>
|
<FormField>
|
||||||
<Field
|
<Field
|
||||||
label='صورالمنتج'
|
label='صور المنتج'
|
||||||
color='info'
|
color='info'
|
||||||
icon={mdiUpload}
|
icon={mdiUpload}
|
||||||
path={'products/product_images'}
|
path={'products/product_images'}
|
||||||
@ -1082,7 +1082,7 @@ const EditProducts = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField label='متاحللبيع' labelFor='is_active'>
|
<FormField label='متاح للبيع' labelFor='is_active'>
|
||||||
<Field
|
<Field
|
||||||
name='is_active'
|
name='is_active'
|
||||||
id='is_active'
|
id='is_active'
|
||||||
@ -1121,7 +1121,7 @@ const EditProducts = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField label='organizations' labelFor='organizations'>
|
<FormField label='المنظمة' labelFor='organizations'>
|
||||||
<Field
|
<Field
|
||||||
name='organizations'
|
name='organizations'
|
||||||
id='organizations'
|
id='organizations'
|
||||||
@ -1168,9 +1168,9 @@ const EditProducts = () => {
|
|||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Submit" />
|
<BaseButton type="submit" color="info" label="حفظ" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="إعادة تعيين" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/products/products-list')}/>
|
<BaseButton type='reset' color='danger' outline label='إلغاء' onClick={() => router.push('/products/products-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@ -498,10 +498,10 @@ const EditProductsPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Edit products')}</title>
|
<title>{getPageTitle('تعديل المنتج')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'Edit products'} main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'تعديل المنتج'} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -643,11 +643,11 @@ const EditProductsPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="اسمالمنتج"
|
label="اسم المنتج"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="product_name"
|
name="product_name"
|
||||||
placeholder="اسمالمنتج"
|
placeholder="اسم المنتج"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -680,11 +680,11 @@ const EditProductsPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="رمزالمنتج"
|
label="رمز المنتج"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="sku"
|
name="sku"
|
||||||
placeholder="رمزالمنتج"
|
placeholder="رمز المنتج"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -760,12 +760,12 @@ const EditProductsPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="سعرالتكلفةالحقيقي"
|
label="سعر التكلفة"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="cost_price"
|
name="cost_price"
|
||||||
placeholder="سعرالتكلفةالحقيقي"
|
placeholder="سعر التكلفة"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -798,12 +798,12 @@ const EditProductsPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="سعرالبيع"
|
label="سعر البيع"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="sale_price"
|
name="sale_price"
|
||||||
placeholder="سعرالبيع"
|
placeholder="سعر البيع"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -836,12 +836,12 @@ const EditProductsPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="سعرالبيعالسابق"
|
label="سعر البيع السابق"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="sale_price_backup"
|
name="sale_price_backup"
|
||||||
placeholder="سعرالبيعالسابق"
|
placeholder="سعر البيع السابق"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -874,12 +874,12 @@ const EditProductsPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="سعرالتكلفةالسابق"
|
label="سعر التكلفة السابق"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="cost_price_backup"
|
name="cost_price_backup"
|
||||||
placeholder="سعرالتكلفةالسابق"
|
placeholder="سعر التكلفة السابق"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -912,12 +912,12 @@ const EditProductsPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="السعربالدولار"
|
label="السعر بالدولار"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="usd_price"
|
name="usd_price"
|
||||||
placeholder="السعربالدولار"
|
placeholder="السعر بالدولار"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -950,12 +950,12 @@ const EditProductsPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="الكميةبالمخزون"
|
label="الكمية بالمخزون"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="stock_quantity"
|
name="stock_quantity"
|
||||||
placeholder="الكميةبالمخزون"
|
placeholder="الكمية بالمخزون"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -988,12 +988,12 @@ const EditProductsPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="حدالتنبيهلنقصالمخزون"
|
label="حد التنبيه لنقص المخزون"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="low_stock_threshold"
|
name="low_stock_threshold"
|
||||||
placeholder="حدالتنبيهلنقصالمخزون"
|
placeholder="حد التنبيه لنقص المخزون"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -1043,7 +1043,7 @@ const EditProductsPage = () => {
|
|||||||
|
|
||||||
<FormField>
|
<FormField>
|
||||||
<Field
|
<Field
|
||||||
label='صورالمنتج'
|
label='صور المنتج'
|
||||||
color='info'
|
color='info'
|
||||||
icon={mdiUpload}
|
icon={mdiUpload}
|
||||||
path={'products/product_images'}
|
path={'products/product_images'}
|
||||||
@ -1079,7 +1079,7 @@ const EditProductsPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField label='متاحللبيع' labelFor='is_active'>
|
<FormField label='متاح للبيع' labelFor='is_active'>
|
||||||
<Field
|
<Field
|
||||||
name='is_active'
|
name='is_active'
|
||||||
id='is_active'
|
id='is_active'
|
||||||
@ -1118,7 +1118,7 @@ const EditProductsPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField label='organizations' labelFor='organizations'>
|
<FormField label='المنظمة' labelFor='organizations'>
|
||||||
<Field
|
<Field
|
||||||
name='organizations'
|
name='organizations'
|
||||||
id='organizations'
|
id='organizations'
|
||||||
@ -1165,9 +1165,9 @@ const EditProductsPage = () => {
|
|||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Submit" />
|
<BaseButton type="submit" color="info" label="حفظ" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="إعادة تعيين" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/products/products-list')}/>
|
<BaseButton type='reset' color='danger' outline label='إلغاء' onClick={() => router.push('/products/products-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@ -34,9 +34,9 @@ const ProductsTablesPage = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
|
||||||
const [filters] = useState([{label: 'اسمالمنتج', title: 'product_name'},{label: 'رمزالمنتج', title: 'sku'},{label: 'الباركود', title: 'barcode'},
|
const [filters] = useState([{label: 'اسم المنتج', title: 'product_name'},{label: 'رمز المنتج', title: 'sku'},{label: 'الباركود', title: 'barcode'},
|
||||||
{label: 'الكميةبالمخزون', title: 'stock_quantity', number: 'true'},{label: 'حدالتنبيهلنقصالمخزون', title: 'low_stock_threshold', number: 'true'},
|
{label: 'الكمية بالمخزون', title: 'stock_quantity', number: 'true'},{label: 'حد التنبيه لنقص المخزون', title: 'low_stock_threshold', number: 'true'},
|
||||||
{label: 'سعرالتكلفةالحقيقي', title: 'cost_price', number: 'true'},{label: 'سعرالبيع', title: 'sale_price', number: 'true'},{label: 'سعرالبيعالسابق', title: 'sale_price_backup', number: 'true'},{label: 'سعرالتكلفةالسابق', title: 'cost_price_backup', number: 'true'},{label: 'السعربالدولار', title: 'usd_price', number: 'true'},
|
{label: 'سعر التكلفة', title: 'cost_price', number: 'true'},{label: 'سعر البيع', title: 'sale_price', number: 'true'},{label: 'سعر البيع السابق', title: 'sale_price_backup', number: 'true'},{label: 'سعر التكلفة السابق', title: 'cost_price_backup', number: 'true'},{label: 'السعر بالدولار', title: 'usd_price', number: 'true'},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -94,28 +94,28 @@ const ProductsTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Products')}</title>
|
<title>{getPageTitle('المنتجات')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Products" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="المنتجات" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/products/products-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/products/products-new'} color='info' label='إضافة منتج'/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label='إضافة تصفية'
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getProductsCSV} />
|
<BaseButton className={'mr-3'} color='info' label='تنزيل CSV' onClick={getProductsCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label='رفع CSV'
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -137,9 +137,9 @@ const ProductsTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title='رفع ملف CSV'
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={'تأكيد'}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -286,10 +286,10 @@ const ProductsNew = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('New Item')}</title>
|
<title>{getPageTitle('إضافة منتج')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Item" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="إضافة منتج" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -366,11 +366,11 @@ const ProductsNew = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="اسمالمنتج"
|
label="اسم المنتج"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="product_name"
|
name="product_name"
|
||||||
placeholder="اسمالمنتج"
|
placeholder="اسم المنتج"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -401,11 +401,11 @@ const ProductsNew = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="رمزالمنتج"
|
label="رمز المنتج"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="sku"
|
name="sku"
|
||||||
placeholder="رمزالمنتج"
|
placeholder="رمز المنتج"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -477,12 +477,12 @@ const ProductsNew = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="سعرالتكلفةالحقيقي"
|
label="سعر التكلفة"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="cost_price"
|
name="cost_price"
|
||||||
placeholder="سعرالتكلفةالحقيقي"
|
placeholder="سعر التكلفة"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -513,12 +513,12 @@ const ProductsNew = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="سعرالبيع"
|
label="سعر البيع"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="sale_price"
|
name="sale_price"
|
||||||
placeholder="سعرالبيع"
|
placeholder="سعر البيع"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -549,12 +549,12 @@ const ProductsNew = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="سعرالبيعالسابق"
|
label="سعر البيع السابق"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="sale_price_backup"
|
name="sale_price_backup"
|
||||||
placeholder="سعرالبيعالسابق"
|
placeholder="سعر البيع السابق"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -585,12 +585,12 @@ const ProductsNew = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="سعرالتكلفةالسابق"
|
label="سعر التكلفة السابق"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="cost_price_backup"
|
name="cost_price_backup"
|
||||||
placeholder="سعرالتكلفةالسابق"
|
placeholder="سعر التكلفة السابق"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -621,12 +621,12 @@ const ProductsNew = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="السعربالدولار"
|
label="السعر بالدولار"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="usd_price"
|
name="usd_price"
|
||||||
placeholder="السعربالدولار"
|
placeholder="السعر بالدولار"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -657,12 +657,12 @@ const ProductsNew = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="الكميةبالمخزون"
|
label="الكمية بالمخزون"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="stock_quantity"
|
name="stock_quantity"
|
||||||
placeholder="الكميةبالمخزون"
|
placeholder="الكمية بالمخزون"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -693,12 +693,12 @@ const ProductsNew = () => {
|
|||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="حدالتنبيهلنقصالمخزون"
|
label="حد التنبيه لنقص المخزون"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="number"
|
type="number"
|
||||||
name="low_stock_threshold"
|
name="low_stock_threshold"
|
||||||
placeholder="حدالتنبيهلنقصالمخزون"
|
placeholder="حد التنبيه لنقص المخزون"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -746,7 +746,7 @@ const ProductsNew = () => {
|
|||||||
|
|
||||||
<FormField>
|
<FormField>
|
||||||
<Field
|
<Field
|
||||||
label='صورالمنتج'
|
label='صور المنتج'
|
||||||
color='info'
|
color='info'
|
||||||
icon={mdiUpload}
|
icon={mdiUpload}
|
||||||
path={'products/product_images'}
|
path={'products/product_images'}
|
||||||
@ -780,7 +780,7 @@ const ProductsNew = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField label='متاحللبيع' labelFor='is_active'>
|
<FormField label='متاح للبيع' labelFor='is_active'>
|
||||||
<Field
|
<Field
|
||||||
name='is_active'
|
name='is_active'
|
||||||
id='is_active'
|
id='is_active'
|
||||||
@ -816,7 +816,7 @@ const ProductsNew = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField label="organizations" labelFor="organizations">
|
<FormField label="المنظمة" labelFor="organizations">
|
||||||
<Field name="organizations" id="organizations" component={SelectField} options={[]} itemRef={'organizations'}></Field>
|
<Field name="organizations" id="organizations" component={SelectField} options={[]} itemRef={'organizations'}></Field>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -828,9 +828,9 @@ const ProductsNew = () => {
|
|||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Submit" />
|
<BaseButton type="submit" color="info" label="حفظ" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="إعادة تعيين" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/products/products-list')}/>
|
<BaseButton type='reset' color='danger' outline label='إلغاء' onClick={() => router.push('/products/products-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user