| Year | Project | Client | Sector | City | Brand | USD | BDT | Status | Actions |
|---|
Switch between Grid, List, Table, or Kanban views. Filter by year/sector/brand/city, search, sort, and see value in USD + BDT at your chosen rate. Add or edit projects—changes save locally.
| Year | Project | Client | Sector | City | Brand | USD | BDT | Status | Actions |
|---|
We supply from UK/EU/USA into Bangladesh with full documentation. Share your item list—brand/equivalent welcome.
Payload sent on Save/Delete (POST to Webhook URL):
{
"action": "create|update|delete",
"project": {
"id": "p123",
"title": "...",
"client": "...",
"year": 2024,
"sector": "power",
"city": "dhaka",
"brand": ["kopa"],
"usd_value": 120000,
"status": "completed",
"applied": "...",
"installed_at": "...",
"procurement": "...",
"image_url": "https://..."
}
}
// server.js
import express from 'express';
import cors from 'cors';
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
const app = express();
app.use(cors());
app.use(express.json());
const db = await open({ filename: './projects.db', driver: sqlite3.Database });
await db.exec(`CREATE TABLE IF NOT EXISTS projects (
id TEXT PRIMARY KEY,
title TEXT, client TEXT, year INTEGER, sector TEXT, city TEXT,
brand TEXT, usd_value REAL, status TEXT,
applied TEXT, installed_at TEXT, procurement TEXT, image_url TEXT
);`);
app.get('/projects', async (req,res)=>{
const rows = await db.all('SELECT * FROM projects ORDER BY year DESC');
rows.forEach(r=> r.brand = r.brand? r.brand.split(',') : []);
res.json(rows);
});
app.post('/projects', async (req,res)=>{
const { action, project } = req.body;
if(action==='delete'){
await db.run('DELETE FROM projects WHERE id=?', project.id);
return res.json({ ok:true });
}
const p = project;
const brand = Array.isArray(p.brand)? p.brand.join(',') : String(p.brand||'');
await db.run(`INSERT INTO projects(id,title,client,year,sector,city,brand,usd_value,status,applied,installed_at,procurement,image_url)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)
ON CONFLICT(id) DO UPDATE SET
title=excluded.title, client=excluded.client, year=excluded.year,
sector=excluded.sector, city=excluded.city, brand=excluded.brand,
usd_value=excluded.usd_value, status=excluded.status,
applied=excluded.applied, installed_at=excluded.installed_at,
procurement=excluded.procurement, image_url=excluded.image_url`,
[p.id,p.title,p.client,p.year,p.sector,p.city,brand,p.usd_value,p.status,p.applied,p.installed_at,p.procurement,p.image_url]);
res.json({ ok:true, id:p.id });
});
app.listen(8787, ()=> console.log('API on http://localhost:8787'));
Run: npm i express cors sqlite sqlite3 then node server.js. Set Webhook URL to http://yourserver:8787/projects. Click **Load from Backend** to pull, or turn on Auto POST.
// Code.gs
function doGet(){
const sh = SpreadsheetApp.openById('SHEET_ID').getSheetByName('projects');
const rows = sh.getDataRange().getValues();
const [head, ...data] = rows;
const idx = Object.fromEntries(head.map((h,i)=>[h,i]));
const out = data.filter(r=>r[idx.id]).map(r=>({
id: r[idx.id], title:r[idx.title], client:r[idx.client], year:+r[idx.year],
sector:r[idx.sector], city:r[idx.city], brand:String(r[idx.brand]||'').split(','),
usd_value:+r[idx.usd_value], status:r[idx.status], applied:r[idx.applied],
installed_at:r[idx.installed_at], procurement:r[idx.procurement], image_url:r[idx.image_url]
}));
return ContentService.createTextOutput(JSON.stringify({projects:out})).setMimeType(ContentService.MimeType.JSON);
}
function doPost(e){
const body = JSON.parse(e.postData.contents);
const sh = SpreadsheetApp.openById('SHEET_ID').getSheetByName('projects');
const data = sh.getDataRange().getValues();
const map = new Map(data.map(r=>[r[0], r])); // id at col A
const p = body.project;
if(body.action==='delete'){ sh.deleteRow(data.findIndex(r=>r[0]===p.id)+1); return; }
const row = [p.id,p.title,p.client,p.year,p.sector,p.city,(p.brand||[]).join(','),p.usd_value,p.status,p.applied,p.installed_at,p.procurement,p.image_url];
if(map.has(p.id)){ sh.getRange(map.get(p.id).row,1,1,row.length).setValues([row]); }
else { sh.appendRow(row); }
}
Create a Sheet with headers: id,title,client,year,sector,city,brand,usd_value,status,applied,installed_at,procurement,image_url. Deploy as Web App and use the Web App URL as your Webhook.
'Impro Projects',
'public' => false,
'show_ui' => true,
'supports' => ['title','thumbnail'],
]);
});
add_action('rest_api_init', function(){
register_rest_route('impro/v1','/projects', [
['methods'=>'GET','callback'=>function(){
$q = new WP_Query(['post_type'=>'impro_project','posts_per_page'=>-1]);
$out = [];
foreach($q->posts as $p){
$out[] = [
'id'=>$p->post_name,
'title'=>$p->post_title,
'client'=>get_post_meta($p->ID,'client',true),
'year'=>(int)get_post_meta($p->ID,'year',true),
'sector'=>get_post_meta($p->ID,'sector',true),
'city'=>get_post_meta($p->ID,'city',true),
'brand'=>explode(',', get_post_meta($p->ID,'brand',true)),
'usd_value'=>(float)get_post_meta($p->ID,'usd_value',true),
'status'=>get_post_meta($p->ID,'status',true),
'applied'=>get_post_meta($p->ID,'applied',true),
'installed_at'=>get_post_meta($p->ID,'installed_at',true),
'procurement'=>get_post_meta($p->ID,'procurement',true),
'image_url'=>get_post_meta($p->ID,'image_url',true),
];
}
return rest_ensure_response(['projects'=>$out]);
}],
['methods'=>'POST','callback'=>function(WP_REST_Request $req){
$body = $req->get_json_params();
$a = $body['action']; $p = $body['project'];
if($a==='delete'){
$post = get_page_by_path($p['id'], OBJECT, 'impro_project');
if($post) wp_delete_post($post->ID,true);
return ['ok'=>true];
}
$post = get_page_by_path($p['id'], OBJECT, 'impro_project');
if($post){
wp_update_post(['ID'=>$post->ID,'post_title'=>$p['title']]);
$id=$post->ID;
} else {
$id = wp_insert_post(['post_type'=>'impro_project','post_status'=>'publish','post_name'=>$p['id'],'post_title'=>$p['title']]);
}
foreach(['client','year','sector','city','brand','usd_value','status','applied','installed_at','procurement','image_url'] as $k){
update_post_meta($id, $k, is_array($p[$k])? implode(',', $p[$k]) : $p[$k]);
}
return ['ok'=>true,'id'=>$p['id']];
}],
]);
});
Zip as impro-projects-api and upload as a plugin. Endpoint: /wp-json/impro/v1/projects. Use this URL as Webhook; turn on **Auto POST** and click **Load from Backend** to pull.