update seed blend
This commit is contained in:
@@ -1,18 +1,11 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
.cache
|
||||||
.git
|
.git
|
||||||
.gitignore
|
|
||||||
README.md
|
|
||||||
.env
|
|
||||||
.nyc_output
|
|
||||||
coverage
|
|
||||||
.coverage
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode
|
*.log
|
||||||
.idea
|
coverage
|
||||||
*.swp
|
tmp
|
||||||
*.swo
|
.env
|
||||||
*~
|
*.local
|
||||||
|
|||||||
@@ -12,11 +12,6 @@ RUN npm install --only=production --silent
|
|||||||
# Copy source code
|
# Copy source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Use the pre-created non-root user from the Node image
|
|
||||||
# and ensure ownership is correct
|
|
||||||
RUN chown -R node:node /app
|
|
||||||
USER node
|
|
||||||
|
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
|
|
||||||
|
|||||||
@@ -389,6 +389,7 @@ router.get('/products', async (req, res, next) => {
|
|||||||
categoryName: product.category_name,
|
categoryName: product.category_name,
|
||||||
productType: product.product_type,
|
productType: product.product_type,
|
||||||
activeIngredients: product.active_ingredients,
|
activeIngredients: product.active_ingredients,
|
||||||
|
seedBlend: product.seed_blend,
|
||||||
description: product.description,
|
description: product.description,
|
||||||
rateCount: parseInt(product.rate_count),
|
rateCount: parseInt(product.rate_count),
|
||||||
usageCount: parseInt(product.usage_count),
|
usageCount: parseInt(product.usage_count),
|
||||||
|
|||||||
@@ -1,18 +1,11 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
.cache
|
||||||
.git
|
.git
|
||||||
.gitignore
|
|
||||||
README.md
|
|
||||||
.env
|
|
||||||
.nyc_output
|
|
||||||
coverage
|
|
||||||
.coverage
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode
|
*.log
|
||||||
.idea
|
coverage
|
||||||
*.swp
|
tmp
|
||||||
*.swo
|
.env
|
||||||
*~
|
*.local
|
||||||
|
|||||||
@@ -3,20 +3,13 @@ FROM node:18-alpine
|
|||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy package files
|
# Copy package files and install dependencies
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
RUN npm install --silent
|
RUN npm install --silent
|
||||||
|
|
||||||
# Copy source code
|
# Copy source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Use the pre-created non-root user from the Node image
|
|
||||||
# and ensure ownership is correct
|
|
||||||
RUN chown -R node:node /app
|
|
||||||
USER node
|
|
||||||
|
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ const AdminProducts = () => {
|
|||||||
productType: 'granular',
|
productType: 'granular',
|
||||||
activeIngredients: '',
|
activeIngredients: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
seedBlend: [],
|
||||||
rates: [{ applicationType: 'granular', rateAmount: '', rateUnit: 'lbs/1000 sq ft', notes: '' }]
|
rates: [{ applicationType: 'granular', rateAmount: '', rateUnit: 'lbs/1000 sq ft', notes: '' }]
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -201,6 +202,7 @@ const AdminProducts = () => {
|
|||||||
productType: 'granular',
|
productType: 'granular',
|
||||||
activeIngredients: '',
|
activeIngredients: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
seedBlend: [],
|
||||||
rates: [{ applicationType: 'granular', rateAmount: '', rateUnit: 'lbs/1000 sq ft', notes: '' }]
|
rates: [{ applicationType: 'granular', rateAmount: '', rateUnit: 'lbs/1000 sq ft', notes: '' }]
|
||||||
});
|
});
|
||||||
setSelectedProduct(null);
|
setSelectedProduct(null);
|
||||||
@@ -215,6 +217,7 @@ const AdminProducts = () => {
|
|||||||
productType: product.productType || product.customProductType || 'granular',
|
productType: product.productType || product.customProductType || 'granular',
|
||||||
activeIngredients: product.activeIngredients || product.customActiveIngredients || '',
|
activeIngredients: product.activeIngredients || product.customActiveIngredients || '',
|
||||||
description: product.description || product.customDescription || '',
|
description: product.description || product.customDescription || '',
|
||||||
|
seedBlend: product.seedBlend || [],
|
||||||
rates: product.rates && product.rates.length > 0 ? product.rates : [{
|
rates: product.rates && product.rates.length > 0 ? product.rates : [{
|
||||||
applicationType: product.productType || 'granular',
|
applicationType: product.productType || 'granular',
|
||||||
rateAmount: product.customRateAmount || '',
|
rateAmount: product.customRateAmount || '',
|
||||||
@@ -340,6 +343,67 @@ const AdminProducts = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Seed blend editor */}
|
||||||
|
{formData.productType === 'seed' && (
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<label className="block text-sm font-medium text-gray-700">Seed Blend (Cultivars + %)</label>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setFormData({ ...formData, seedBlend: [...(formData.seedBlend||[]), { cultivar: '', percent: '' }] })}
|
||||||
|
className="text-sm text-blue-600 hover:text-blue-800"
|
||||||
|
>
|
||||||
|
+ Add Cultivar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{(formData.seedBlend && formData.seedBlend.length > 0) ? (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{formData.seedBlend.map((row, idx) => (
|
||||||
|
<div key={idx} className="grid grid-cols-6 gap-2 items-center">
|
||||||
|
<input
|
||||||
|
className="col-span-4 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
placeholder="Cultivar name"
|
||||||
|
value={row.cultivar}
|
||||||
|
onChange={(e)=> {
|
||||||
|
const next = [...formData.seedBlend];
|
||||||
|
next[idx] = { ...next[idx], cultivar: e.target.value };
|
||||||
|
setFormData({ ...formData, seedBlend: next });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
className="col-span-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
placeholder="%"
|
||||||
|
value={row.percent}
|
||||||
|
onChange={(e)=> {
|
||||||
|
const next = [...formData.seedBlend];
|
||||||
|
next[idx] = { ...next[idx], percent: e.target.value };
|
||||||
|
setFormData({ ...formData, seedBlend: next });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="text-red-600"
|
||||||
|
onClick={()=> {
|
||||||
|
const next = (formData.seedBlend||[]).filter((_,i)=> i!==idx);
|
||||||
|
setFormData({ ...formData, seedBlend: next });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="text-xs text-gray-600">
|
||||||
|
Total: {((formData.seedBlend||[]).reduce((s,r)=> s + (parseFloat(r.percent)||0), 0)).toFixed(1)}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-sm text-gray-500">No cultivars added yet.</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Application Rates */}
|
{/* Application Rates */}
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center mb-2">
|
<div className="flex justify-between items-center mb-2">
|
||||||
@@ -912,4 +976,4 @@ const AdminProducts = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AdminProducts;
|
export default AdminProducts;
|
||||||
|
|||||||
@@ -1471,7 +1471,7 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Editor component for seed blends
|
// Editor component for seed blends
|
||||||
const SeedBlendEditor = ({ value = [], onChange }) => {
|
function SeedBlendEditor({ value = [], onChange }) {
|
||||||
const [rows, setRows] = React.useState(value || []);
|
const [rows, setRows] = React.useState(value || []);
|
||||||
React.useEffect(()=>{ onChange && onChange(rows); }, [rows]);
|
React.useEffect(()=>{ onChange && onChange(rows); }, [rows]);
|
||||||
const addRow = () => setRows([...(rows||[]), { cultivar: '', percent: '' }]);
|
const addRow = () => setRows([...(rows||[]), { cultivar: '', percent: '' }]);
|
||||||
@@ -1502,6 +1502,6 @@ const SeedBlendEditor = ({ value = [], onChange }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Products;
|
export default Products;
|
||||||
|
|||||||
Reference in New Issue
Block a user